Thomas
Thomas

Reputation: 203

C++ and standard containers: memory addresses of members

I am currently having some trouble with my understanding of the memory management in standard containers, especially std::vector.

It is clear to me that std::vector will resize after adding an element to it if there is not enough space reserved, therefore moving each element and changing the addresses in memory. My question is now: what happens to the element's member variables?

My problem is based on the idea that for my game engine I am currently managing scenes in a std::vector. The scenes are managed by a Scene manager class that contains a std::vector of Scenes. Adding of scenes looks like this:

    std::vector<Scene> scenes;
    Scene* active;
...
        Scene scene;
        scenes.emplace_back(scene);
        active = &scenes.back();

The scenes are stack-allocated and will be thrown away after leaving the method. For exposing the currently added scene to the outside I am storing a pointer to the back of the std::vector which is the newly inserted element.

Scenes contain various members, for example an instance of the Light class. I also expose pointers to those elements to the outside for various reasons. My problem was that I tried to use those pointers in the constructor of the Scene which got constructed in the Scene manager. After adding the object to the std::vector a new object seems to be constructed even if the Scene constructor doesn't seem to be called. The "active" member now contains an different memory address than the actual Scene object I allocated before. As the vector needs to resize I am clear with that I think.

But what happens to the members of the scene? The original Scene will be destructed, the active element got another memory address. That means it points to an whole new element because of the internal resize and that new element has new members and are in my case those I want to work with.

Am I right with my understanding?

My second question includes how I should handle situations like that where I want to expose pointers to members of objects stored in a std::vector with unknown size. My current method of choice works just fine but I am not sure if it is the right way to do:

In the Scene class there is an onActivate event method which will just be called after the whole resizing and getting the inserted element of the vector has finished. When I switch the active pointer that method will be called, too. That method takes the pointers to the members of the scene and passes them around. It looks something like that:

void postConstruct() {
    std::cout << "postConstruct: " << &el << std::endl;
}

And will be called at the right place in the Scene manager which currently is a friend to the Scene class because those events should not be exposed to the outer world.

    active->postConstruct();

Is this the right way to go?

Upvotes: 1

Views: 564

Answers (2)

Nishant Singh
Nishant Singh

Reputation: 4915

In case the std::vector is resized, the elements will be either moved using element's move constructor if the move constructor is declared as noexcept or it would be copied using element's copy constructor to the newly allocated position.

Whether the member pointers will be same after reallocation will depend upon how the move constructor or copy constructor is implemented for the element being inserted.

I would suggest using index than Scene* to access the elements in std::vector or use std::list if you want to use Scene*

Upvotes: 2

Caleth
Caleth

Reputation: 63247

When a vector is expanded, all iterators, pointers and references to elements become invalid. The only defined thing you can do with an invalid pointer or iterator is overwrite it with another value, and there is nothing you can do with an invalid reference. Even comparing the value to some other value makes your program ill-formed.

When you do

Scene scene; 
scenes.emplace_back(scene); 
active = &scenes.back();

You have two Scene objects. One is a local variable, the other is in the vector, and was copied from scene. I'm not sure that you are aware of this distinction, you probably only want one Scene object. Either all Scenes live in scenes, or you change it to be a std::vector<Scene *>, or a std::vector<std::reference_wrapper<Scene>>. If you do the latter, make sure to remove values before they are destroyed.

The language provided copy constructor will simply copy the value of each member, which in the case of pointers, will tend to be the wrong thing. You can explicitly define a copy constructor for Scene to control exactly what happens, e.g. "deep copy" pointer members.

class Copyable
{
    int* ptr;
public: 
    Copyable() : ptr(new int) {}
    ~Copyable() { delete ptr; }
    Copyable(const Copyable & other) : ptr(new int(*other.ptr)) {} // deep copy
    Copyable& operator=(const Copyable & other)
    {
        *ptr = *other.ptr;
        return *this;
    }
};

Alternatively, you can prohibit the copying of Scenes by defining the copy constructor as = deleted

class UnCopyable
{
    int* ptr;
public: 
    UnCopyable() : ptr(new int) {}
    ~UnCopyable() { delete ptr; }
    UnCopyable(const UnCopyable & other) = delete;
    UnCopyable& operator=(const UnCopyable & other) = delete;
};

Upvotes: 1

Related Questions