-2

I'm having difficulties explaining the problem with words. But I have replicated my problem in a minimal example. Basically I have a main class holding a vector of a different class objects. This main class has a member function that creates an object, add it to the vector, and returns a pointer to the new object in the vector. When I then try to access the values of the object from the pointer that was returned, the values are as if they were never set.

Here is the code of a minimal example:

EDIT:

As some pointed out I had value = value in the member function. This was obviously a mistake, and I've edited to _value = value

#include <iostream>
#include <vector>

class MySecondaryClass
{
public:
    MySecondaryClass(int aValue) { _aValue = aValue; }
    ~MySecondaryClass() {}
    int _aValue;
};

class MyMainClass
{
private:
    std::vector<MySecondaryClass> _secClassVec;
public:
    MyMainClass() {}
    ~MyMainClass(){}

    MySecondaryClass* addNewSecClass(int aValue) { 
        MySecondaryClass newSecClass(aValue);
        _secClassVec.push_back(newSecClass);
        return &_secClassVec[_secClassVec.size() - 1];
    }
};


int main() {

    MyMainClass mainObject;
    MySecondaryClass* secObject1_ptr = mainObject.addNewSecClass(1);
    MySecondaryClass* secObject2_ptr = mainObject.addNewSecClass(2);
    MySecondaryClass* secObject3_ptr = mainObject.addNewSecClass(3);

    std::cout << secObject1_ptr->_aValue << std::endl;
    std::cout << secObject2_ptr->_aValue << std::endl;
    std::cout << secObject3_ptr->_aValue << std::endl;

    return 0;
}

And the output:

-572662307
-572662307
3

Expected output is:

1
2
3

Can someone explain what I'm doing wrong here?

remi
  • 937
  • 3
  • 18
  • 45
  • @πάνταῥεῖ This dup is nothing to do with the entire problem (the last pointer should still point to a valid object). – StoryTeller - Unslander Monica Oct 26 '16 at 20:46
  • In my original problem, the last pointer was actually working correctly. And I don't really understand what happened here. But I will have a look at the (maybe) duplicate. – remi Oct 26 '16 at 20:48
  • While you're at it, name your constructor parameter as something other than your member. There is name hiding occurring in your c'tor body. You assign the parameter to itself, whilst your member remains with indeterminate value. Crank up the warning level. – StoryTeller - Unslander Monica Oct 26 '16 at 20:51
  • This was a mistake when creating the minimal example. Which I did not notice because I expected a wrong result. Now the last pointer is in fact valid. – remi Oct 26 '16 at 21:03
  • Now that you edited, you may want to check the output again. The last element should always be valid (the others are victims of UB). And now it's a duplicate. – StoryTeller - Unslander Monica Oct 26 '16 at 21:03
  • Output was edited right after. Ok, thank you. – remi Oct 26 '16 at 21:06
  • But is it duplicate? In that other problem, the value is defined in the scope of a function, and I would say it makes sense it is invalid. But here my object is defined in a member of a class object. – remi Oct 26 '16 at 21:09
  • It's still a matter of dangling pointers. You can see my edit for an optional approach to this. – StoryTeller - Unslander Monica Oct 26 '16 at 21:19
  • Well what is the minus for anyway? Was the question that bad? – remi Oct 26 '16 at 21:34

3 Answers3

1

Name your constructor argument something that doesn't hide your member in the constructor body, and start using initialzier lists:

MySecondaryClass(int aValue_) : aValue(aValue_)
{ }

Also remember that pushing back into a vector may invalidate iterators (and pointers to its elements).


Well, now that you edited you question. I can only suggest you return a proxy object which behaves like a pointer.

class MySecondaryClassPtr
{
  MyMainClass& mmc_;
  std::size_t  pos_;

  public:
  MySecondaryClassPtr(MyMainClass& mmc, std::size_t pos) :
   msc_(msc), pos_(pos)
  {}

  MySecondaryClass& operator*()  { return mmc_._secClassVec[pos_]; }
  MySecondaryClass* operator->() { return &mmc_._secClassVec[pos_]; }  

};

This "smart pointer" proxy object is tied to the lifetime of your MyMainClass object, rather than the internal storage of the vector.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • Actually if you use initializers list, you do not have to name the parameters differently and it will work. Nevertheless, it is sure a good convention to follow. – EmDroid Oct 26 '16 at 21:01
  • Interesting solution. Complicated for me to understand, but trying to digest it:) – remi Oct 26 '16 at 21:22
  • @remi000, I admit it uses some non-trivial features of c++, both at least the code that uses the pointers will not need to be changed too much :) – StoryTeller - Unslander Monica Oct 26 '16 at 21:24
  • Or instead of the MyMainClass ref it can directly hold the ref to the vector, that should work as well. Because the address of the vector itself should not change even if the vector is resized. (and if templatized, you can make it a general "vector position pointer" ;) ) – EmDroid Oct 26 '16 at 21:26
  • yes, I'm trying to understand what this overloading of operators actually is doing. Little bit new for me. – remi Oct 26 '16 at 21:26
  • 1
    @remi000, you should not that **axalis** and I are suggesting pretty much the same thing, just with different syntactic sugar on top :) Operator overloading only looks daunting at first, but it can be a really good way to write intuitive operations. It's worth the hassle to learn. – StoryTeller - Unslander Monica Oct 26 '16 at 21:31
1

First problem is this in the constructor:

MySecondaryClass(int aValue) { aValue = aValue; }

what it does is that is assigns the parameter aValue to itself, i.e. does not set the aValue attribute of the class. This can be fixed by either:

MySecondaryClass(int aValue) { this->aValue = aValue; }

or

MySecondaryClass(int aValue): aValue(aValue) {}

But in general, this is why it is not recommended to name the parameters the same as the attributes.

Second problem is, that the call of:

MySecondaryClass* secObject3_ptr = mainObject.addNewSecClass(3);

might invalidate the memory assigned to secObject1_ptr and secObject2_ptr (if the vector resizes) - basically every call to mainObject.addNewSecClass() might invalidate the previously returned pointers (technically it is undefined behavior).

EDIT

To answer the comment question: If you want to store the actual objects in the class and access them, you need to do it differently, either by index, or in some cases you can keep a map by id, depending on what you need. But you normally cannot keep the pointers to the changing data structure.

One possibility:

class MyMainClass
{
private:
    std::vector<MySecondaryClass> _secClassVec;
public:
    MyMainClass() {}
    ~MyMainClass() {}

    size_t addNewSecClass(int aValue) {
        MySecondaryClass newSecClass(aValue);
        _secClassVec.push_back(newSecClass);
        return _secClassVec.size() - 1;
    }

    const MySecondaryClass& operator [](size_t idx) const
    {
        return _secClassVec[idx];
    }
};


int main() {

    MyMainClass mainObject;

    size_t secObject1_idx = mainObject.addNewSecClass(1);
    size_t secObject2_idx = mainObject.addNewSecClass(2);
    size_t secObject3_idx = mainObject.addNewSecClass(3);

    std::cout << mainObject[secObject1_idx].aValue << std::endl;
    std::cout << mainObject[secObject2_idx].aValue << std::endl;
    std::cout << mainObject[secObject3_idx].aValue << std::endl;

    return 0;
}

However also this will only work, if you only add to the vector. Removal will invalidate the indices after the removed item as well.

Another possibility might be to allocate dynamically and only keep the pointers in the vector - then the original object address will not change when adding items. But then comes the necessity to manage the lifetime, copy construction etc., which can be somewhat mitigated by using a smart pointer (e.g. shared_ptr).

EmDroid
  • 5,918
  • 18
  • 18
  • First problem I've edited in the question. It was originally _value but after I moved it from private to public I changed the name... But do you have good suggestion for how to solve the second problem without redesigning the structure too much? – remi Oct 26 '16 at 21:01
  • Yes, then the second problem still remains. Note that if you print the pointers immediately after receiving them, it will work, but not if you first get all 3 of them and then print. – EmDroid Oct 26 '16 at 21:03
  • Well in my case, I was hoping to have a main class hold a vector of a bunch of classes. And then to access them like I'm trying to do... So there is no magical way of "locking" the address of an object in a vector.. or something? – remi Oct 26 '16 at 21:06
  • Ahaa. Nice idea of assigning that [] operator also! That will give me a cleaner code for my original problem. Thanks @axalis ! – remi Oct 26 '16 at 21:19
  • You might also consider forwarding the vector::size() to know how many objects you have, or even inherit the factory class from std::vector directly (but that would also expose the push_back methods etc.). – EmDroid Oct 26 '16 at 21:23
  • Or instead of inheriting publicly, you inherit privately, and forward only the `size` member function. – StoryTeller - Unslander Monica Oct 26 '16 at 21:28
0

When you push into a vector it has to allocate memory for the new element.

This allocation demand might not be satisfiable at the very location in memory where the underlaying array is currently at, so the vector has to allocated contiguous memory for its whole content elsewhere, move the old content, add the new one and free the previously allocated block.

So pointers into the vector's memory can become invalid every time you change the vector.

Kevin Krammer
  • 5,159
  • 2
  • 9
  • 22