-4

I have a template class that I am testing:

class SparseMat {
private: 
   FHvector<FHlist<MatNode<Object>>> matrix;
   int numOfRows, numOfCols;
   const Object defaultValue;
public:
   SparseMat(int r, int c, const Object& defaultVal);
   const Object & get(int r, int c) const;
   bool set(int r, int c, const Object& x);
};

template <class Object>
SparseMat<Object>::SparseMat(int r, int c, const Object& defaultVal) : defaultValue(defaultVal) {
   numOfRows = r;
   numOfCols = c;

   matrix.resize(numOfRows);

   for (int counter = 0; counter < numOfRows; counter++) {
      FHlist<MatNode<Object>> currentRow;
      matrix.push_back(currentRow);
   }
}

template <class Object>
bool SparseMat<Object>::set(int r, int c, const Object& x) {
   if (r >= numOfRows || r < 0 || c < 0 || c >= numOfCols) {
      return false;
   }
   if (r == 9 && c == 9) {
      cout << x << endl;
   }
   if (r == 9 && c == 9) {
      cout << x << endl;
   }
   for (FHlist<MatNode<Object>>::iterator iter = matrix[r].begin(); iter != matrix[r].end(); ++iter) {
      if ((*iter).getCol() == c) {
         if (x == defaultValue) {
            matrix[r].erase(iter);
            return true;
         }
         else {
            (*iter).data = x;
            return true;
         }
      }
   } 
   matrix[r].push_back(MatNode<Object>(c, x));
   return true;
}

template <class Object>
const Object & SparseMat<Object>::get(int r, int c) const {
   if (r >= numOfRows || r < 0 || c < 0 || c >= numOfCols) {
      throw OutOfBoundsException();
   }
   FHlist<MatNode<Object>> wantedRow = matrix[r];
   for (FHlist<MatNode<Object>>::iterator iter = wantedRow.begin(); iter != wantedRow.end(); ++iter) {
      if ((*iter).getCol() == c) {
         return (*iter).getData();
      }
   }
   return NULL;
}

MatNode is as follows:

template <class Object>
class MatNode
{
protected:
   int col;

public:
   Object data;
   MatNode(int cl = 0, Object dt = Object()) : col(cl), data(dt) { }
   int getCol() const { return col; }
   const Object & getData() const {return data; }
};

The immensely strange thing is my two outputs print two different things. The first prints 21, as expected. The second prints out some random float, which is definitely not expected as I have changed nothing with x between the two outputs.

#include <iostream>
using namespace std;
#include "FHsparseMat.h"

#define MAT_SIZE 100000
typedef SparseMat<float> SpMat;

int main()
{

   SpMat mat(MAT_SIZE, MAT_SIZE, 0); 
   mat.set(3, 9, 21);
   cout << mat.get(3, 9) << endl;
   mat.set(9, 9, 21);
   cout << mat.get(9, 9) << endl;
   mat.set(9, 9, mat.get(3,9)); 
   cout << mat.get(9, 9) << endl;
}

Here is my tester. If I replace mat.get(3,9) with the hard coded value of 21, the issue disappears, if that helps.

avi1234
  • 63
  • 2
  • 8
  • Where's the code for `mat.get()`? – iBug Jan 24 '18 at 06:11
  • @iBug Added it in – avi1234 Jan 24 '18 at 06:12
  • 3
    Please learn how to create a [mcve]. – iBug Jan 24 '18 at 06:14
  • The two ifs "r == 9 && c == 9" are identical. It seems the first one should be "r == 3 && c == 9" to be what you are claiming right? – AdvSphere Jan 24 '18 at 06:14
  • @iBug I read over the guidelines; can you tell me what I'm missing? I'm still pretty new to StackOverflow, so not too sure – avi1234 Jan 24 '18 at 06:18
  • People are not able to recreate your issue from the code you've given. You are surely missing something. e.g. the layout of `class SparseMat`. – iBug Jan 24 '18 at 06:19
  • @AdvSphere The two ifs are identical, but the outputs are different. That is what is so strange to me. – avi1234 Jan 24 '18 at 06:19
  • 2
    **Minimal** as in the code produces the error and contains nothing that doesn't produce the error. **Complete** as in it compiles and runs or cannot compile but will once the error is resolved. **Verifiable** as in it produces the error. These can be tricky to write, but odds are really good that as you approach the goal you will see the error, groan, and fix it without having to ask a question. – user4581301 Jan 24 '18 at 06:22
  • When you say "The first prints 21, as expected." I understand `mat.set(3, 9, 21);` is the one you are talking about. But I don't see how this is expected, since it would need to be `mat.set(9, 9, 21);` in order to get that result. – AdvSphere Jan 24 '18 at 06:23
  • Nonsensical output like you're claiming implies undefined behavior. There are lots of causes for that. Since the issue goes away when you eliminate `get` that's where I'd look. – Mark Ransom Jan 24 '18 at 06:24
  • I updated the question to hopefully fit the qualifications better. Unfortunately still do not know where my issue is. – avi1234 Jan 24 '18 at 06:24
  • @AdvSphere mat.set(3,9,21) places 21 into the position (3,9) in a matrix. Then, I'm retrieving 21 from that position and placing it into position (9,9). – avi1234 Jan 24 '18 at 06:26
  • @AdvSphere I am referring to standard output. Running my program returns 21 and some random float that changes every run. Am I misunderstanding something? – avi1234 Jan 24 '18 at 06:30
  • Did you forget to set the size on `matrix`? `matrix[r]` might be out of bounds if `matrix.size()` doesn't match `numOfRows` – Ben Voigt Jan 24 '18 at 06:32
  • @BenVoigt matrix.size() does match numOfRows, I just confirmed. – avi1234 Jan 24 '18 at 06:34
  • @MarkRansom mat.get() seems to work fine though when I test it in main(). – avi1234 Jan 24 '18 at 06:35
  • Obviously your problem is in get then. I would have the for loop in that method use the same loop as you have in set. `for (FHlist>::iterator iter = matrix[r].begin(); iter != matrix[r].end(); ++iter) {if ((*iter).getCol() == c) { return (*iter).getData();` You can try that. If that doesn't work is because your set method is not setting the value. Just in case since I don't know how `MatNode` from `matrix[r]` copy constructor was implemented. – AdvSphere Jan 24 '18 at 06:35
  • Note your code will be a lot clearer if you write `iter->getCol()` instead of `(*iter)->getCol()` – Ben Voigt Jan 24 '18 at 06:36
  • I suggest you make a function that prints out all the `(r, c, v)` triples, and call it before and after you call `set()`. – Ben Voigt Jan 24 '18 at 06:37
  • are you positive the `mat.set(3, 9, 21);` is setting the value? why for debugging just return true and false in the if/else inside the loop. Is possible the value already existed. Is hard to know with the code you are providing since more info is needed. – AdvSphere Jan 24 '18 at 06:47
  • @AdvSphere When I call mat.get(3,9) directly in main(), it prints out 21, so it does set the value correctly. Also, trying to change it to matrix[r] in the loop gives me the following error: error C2440: 'initializing': cannot convert from 'FHlist>::const_iterator' to 'FHlist>::iterator'. Which also makes no sense, because I use the exact same code in set(). – avi1234 Jan 24 '18 at 06:54
  • You are saying you are running the equivalent to this code: `mat.set(3, 9, 21); assert(mat.get(3,9) == 21); mat.set(9, 9, 21); assert(mat.get(9, 9)==21); mat.set(9, 9, mat.get(3, 9)); assert(mat.get(9, 9) != 21);` If you are not running it, please do so and let me know the output please. – AdvSphere Jan 24 '18 at 07:00
  • Yes, that is exactly the situation. – avi1234 Jan 24 '18 at 07:06
  • @avi1234 `r >= numOfRows` -- If that `FHvector` class knows its own size by calling a `size()` or equivalent member function, why do you feel it necessary to maintain superfluous variables such as `numOfRows`? All that will do is increase the chance of bugs occurring due to not adjusting the `numOfRows` to reflect the proper dimensions. – PaulMcKenzie Jan 24 '18 at 07:09
  • @PaulMcKenzie I see your point, but nowhere do I change the number of rows in the matrix, so I don't think that is the cause of the bug in this situation – avi1234 Jan 24 '18 at 07:15
  • @avi1234 -- Your code is too large and incomplete to assume anything. Use the proper functions such as `size()`, not member variables you need to keep track of. It is the errors you don't see because of false assumptions that are the toughest ones to diagnose. – PaulMcKenzie Jan 24 '18 at 07:15
  • I would suggest, post your complete main() to see how you are testing and getting the error. It could be your matrix is caching data you don't want and giving you unexpected results depending on the order you call the methods. – AdvSphere Jan 24 '18 at 07:18
  • @PaulMcKenzie Replacing did not seem to affect anything. – avi1234 Jan 24 '18 at 07:23
  • `matrix[r].erase(iter);` -- Is this supposedly reducing the number of columns, i.e. `numOfCols`? If so, this is what I am referring to -- where do you update `numOfCols`? Why not simply use `matrix[r].size()`? Also, if what that `FHvector` class has an `at()` function that throws an exception on out-of-bounds conditions, why not use it to ensure you are not going out of bounds? – PaulMcKenzie Jan 24 '18 at 07:23
  • @AdvSphere Added the full main. Perhaps the typedef? (although I don't see how/why it would make a difference) – avi1234 Jan 24 '18 at 07:23
  • Could you delete the `mat.set(9, 9, 21);` (4th line) in your main and tell me the result please? of `mat.get(9, 9, mat.get(3,9));`? – AdvSphere Jan 24 '18 at 07:24
  • And if that still does not work then print the result of calling `mat.get(3,9))` twice or more. I have strong feeling is your matrix implementation caching wrong data. – AdvSphere Jan 24 '18 at 07:30
  • @PaulMcKenzie The reason I use numOfCols versus matrix[r].size() is because of the type of matrix I am building. For example, if I call mat.set(9,9,21) and then mat.get(9,9), matrix[9].size() will only be 1, so mat.get() will not be successfully called. – avi1234 Jan 24 '18 at 07:31
  • @avi1234 `matrix[9].size() will only be 1` -- I don't understand. So the container class is telling you that 9 is out-of-bounds, but you go ahead and force the issue anyway by trying to access the 9th element? Also, where is your `SparseMat` constructor? – PaulMcKenzie Jan 24 '18 at 07:35
  • @AdvSphere To your first inquiry, the output is 21, 0, random number. To the second, the output is 21, 21, 0, random number (this is with the 4th line still deleted). – avi1234 Jan 24 '18 at 07:35
  • 1
    @PaulMcKenzie I am constructing a sparse matrix, so I am creating a numOfRows long vector, with each entry a list with initial length of 0. Added the constructor to hopefully clarify things. – avi1234 Jan 24 '18 at 07:40
  • Ok, that's weird. Just for the sake of it could you assign `const float result { mat.get(3, 9)}; mat.set(9, 9, result); mat.get(9, 9);` – AdvSphere Jan 24 '18 at 07:41
  • @AdvSphere That seems to work. So I guess it is an issue with get()? – avi1234 Jan 24 '18 at 07:47
  • Yes, do this please `const float& result {mat.get(3, 9)};` Does that work as well? – AdvSphere Jan 24 '18 at 07:48
  • @avi1234 -- In your constructor you do this: `matrix.resize(numOfRows);`, which is supposed to resize the matrix to the appropriate size. Then you do this in a loop: `matrix.push_back(currentRow);` -- which would add *more* rows to the back of the matrix, thus increasing the size. So `numOfRows` would seem inconsistent, no? Also, what is `FHvector`? Is it really a `std::vector` under the covers? And if this is the case, for laughs, why not put `assert(matrix.size() == numOfRows);` in your code. – PaulMcKenzie Jan 24 '18 at 07:48
  • The output in main() is 21, but inside set(), the two outputs are both random floats. – avi1234 Jan 24 '18 at 07:49
  • Could you show me the signature for the matrix method getData() please? Don't think is returning a reference. – AdvSphere Jan 24 '18 at 07:51
  • @AdvSphere Could you explain what you mean by signature? I have the MatNode class in my post. – avi1234 Jan 24 '18 at 07:54
  • @PaulMcKenzie I see your point, but I don't see how it could possibly create an issue/error. At worst, it would just cause excess memory use, no? By increasing the size, numOfRows would always be less than matrix.size(), so by checking against numOfRows, I am by conjunction always going to be less than matrix.size(). I know, it's terrible code design, but I want to focus on getting this bug fixed first. And FHvector is just a provided version of std::vector, so same basic functionality. – avi1234 Jan 24 '18 at 07:56
  • Just posted the possible fix, if it works please mark the question as solved please :). – AdvSphere Jan 24 '18 at 07:57
  • @avi1234 -- You are not just allocating more rows than necessary -- you are filling the first `numOfRows` rows with data that is initialized to whatever a `FHlist>` default initializes itself to. If you now start to access those rows that you never explicitly initialized yourself, what will happen? How will that affect the program flow or calculation? Everything we have stated so far you say doesn't make a difference -- thus the only thing to do is point out the obvious bugs -- maybe one is the reason or part of the reason for the issue. – PaulMcKenzie Jan 24 '18 at 08:01
  • @PaulMcKenzie How can I possibly access the rows I don't explicitly initialize though? If I filled the first numOfRows with the default FHlist, and am always checking against numOfRows, why would I ever go past numOfRows in my vector? – avi1234 Jan 24 '18 at 08:09
  • @avi1234 -- I am not talking about out-of-bounds accesses. I'm talking about treating `row 0`, `row 1`, etc. up to `row[numOfRows-1]` as if they were initialized when they were not. The only rows you actually initialized are `row[numOfRows]` up to `row[matrix.size() - 1]`. Believe me, I have seen this error hundreds of times, where the programmer front-loaded the vector with "garbage" values by using `resize()`, and erroneously calling `push_back` believing they are initializing those entries. – PaulMcKenzie Jan 24 '18 at 08:11

2 Answers2

1

get() has a return type of const Object &.

As a result, the final line of the function

return 0; // source code says NULL but preprocessor replaces that with 0

is returning a dangling reference to a temporary Object implicitly constructed with the value 0.

Using that dangling reference will, of course, cause undefined behavior.

It's not completely clear why that line is reached, but the logic that erases an item if you write the same value to the same location certainly seems suspicious. IMO you should only remove an item when the value written is zero.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thanks for catching the last bit. Your opinion was the intended implementation, but converting it to the correct code did not affect the run result – avi1234 Jan 24 '18 at 06:54
  • @avi1234: I guess you never fixed the first problem I pointed out... because then AdvSphere went and pointed out the exact same thing on `get()`'s other return path. – Ben Voigt Jan 24 '18 at 15:06
0

The issue is that Object MatNode::getData() const is not returning a reference, and you are returning a reference in const Object & SparseMat<Object>::get(int r, int c) const. Change it to:

Object SparseMat<Object>::get(int r, int c) const.

AdvSphere
  • 986
  • 7
  • 15
  • That does fix it! The problem is that I am required to have the header as const Object & SparseMat::get(int r, int c) const. Is there a way to modify the header of getData() to achieve the same result? – avi1234 Jan 24 '18 at 08:05
  • I did, but is there a way to modify the header of getData() instead of the header of get()? – avi1234 Jan 24 '18 at 08:07
  • You can return a reference from getData(), however if you are going to be using primary primitives types is not advisable since the value occupies the same memory space as the address. OTH you could use it for scalability (using for other types). Also make sure when using a reference for an object, the object always exists while you use it. It creates a coupling that you don't want unless you want high performance. – AdvSphere Jan 24 '18 at 08:08
  • @avi1234 First decide whether you want to actually use the actual `Object` and not a copy of the object. If you want to use the actual `Object` instance, then you return a reference, not a copy. – PaulMcKenzie Jan 24 '18 at 08:10
  • @AdvSphere Sorry if this is a dumb question, but what would the syntax for returning a reference from getData() look like? – avi1234 Jan 24 '18 at 08:19
  • No worries :). `const Object& getData() const {return data; }`. Just remember to be careful to use the reference as long as you make sure the object exists. – AdvSphere Jan 24 '18 at 08:21
  • @AdvSphere Aghhh, I tried that, but everything goes bad, even mat.get(3,9). When/how would I know if the object still exists? – avi1234 Jan 24 '18 at 08:26
  • Do: `const Object& getData() const {return data; }` and `const Object & SparseMat::get(int r, int c) const` – AdvSphere Jan 24 '18 at 08:27
  • @AdvSphere That's what I'm doing though. Updated my post to reflect my current code. – avi1234 Jan 24 '18 at 08:31
  • I think is your iterator in get(), it must be const in order to preserve constness from getData(). – AdvSphere Jan 24 '18 at 08:35
  • change to `for(FHlist>::const_iterator iter = matrix[r].begin(); iter != matrix[r].end(); ++iter) {...}` Edit: Added const_iterator – AdvSphere Jan 24 '18 at 08:36
  • @AdvSphere But how can I make it const if I need to move it with ++iter? Yeah, I'm getting error C2678: binary '++': no operator found which takes a left-hand operand of type 'const FHlist>::iterator'. – avi1234 Jan 24 '18 at 08:39
  • You need to make sure the custom iterator has const semantics as well. See this post for more info how to implement it: https://stackoverflow.com/questions/3582608/how-to-correctly-implement-custom-iterators-and-const-iterators – AdvSphere Jan 24 '18 at 08:41
  • @AdvSphere It works!!! I know I'm not supposed to make a post dedicated to thanking, but thank you so much for spending so much time helping me out! – avi1234 Jan 24 '18 at 08:45
  • Glad to help :). – AdvSphere Jan 24 '18 at 08:46
  • @avi1234: `const_iterator` is not the same as a `const` instance of `iterator`. – Ben Voigt Jan 24 '18 at 15:07