c++stdvector

Do vector objects go out of scope when assigned to a class variable?


I'm confused about how scoping and memory persistence works with std::vectors. Consider the following:

#include <string>
#include <vector> 

class Entity
{
     std::string name;
}

class MyClass
{
    std::vector<Entity> class_vec;
    MyClass()
    {
        std::vector<Entity> local_vec(100);
        class_vec = local_vec;
    }
}

int main(argc, char* argv[])
{
     MyClass myclass;
}

If the vector elements are copy by value everything should be OK but if they use move for example, bad things can happen as local_vec will go out of scope. From what I understand local_vec is created on the stack and when the constructor is exited should be removed from it.

In the documentation it says "move is used when assigning vectors when possible" - isn't this asking for problems? When do I need to be worried about a vector assigned to another vector will have its elements go out of scope if they have been created in a certain scope?

A for sure way to cause a problem is if a vector of pointers are used to reference stack objects but that isn't being done here.


Solution

  • If the vector elements are copy by value everything should be OK but if they use move for example, bad things can happen as local_vec will go out of scope. From what I understand local_vec is created on the stack and when the constructor is exited should be removed from it.

    That's not correct. There would be no problem with move semantics in this situation. That being said, the compiler would not be permitted to apply them, so we get a copy.

    std::vector<Entity> local_vec(100);
    class_vec = local_vec;
    

    We have two named vectors: class_vec and local_vec. Both are lvalue references, so we get the copy assignment operator.

    constexpr vector& operator=( const vector& other );
    

    So, at least at a glance, C++ must call the copy assignment operator. Now, aggressive optimization and inlining might help with that, but it can't simply decide it wants to call a different overload.

    That being said, we can force move semantics here with std::move, which turns an lvalue reference into an rvalue reference, communicating to the compiler that we don't plan to use the value after this fact.

    class_vec = std::move(local_vec);
    

    Now we've got a move assignment operator. And this still isn't a problem. In fact, it's probably an improvement. If we assume std::vector is defined something like this

    // Gross oversimplification
    template <typename T>
    class vector {
      T* array;
      int size;
    };
    

    Then the move assignment operator would look something like this.

    // Again, gross oversimplification
    template <typename T>
    vector<T> vector<T>::operator=(vector<T>&& that) {
      this->array = that.array;
      this->size = that.size;
      that.array = std::nullptr;
      that.size = 0;
    }
    

    That is, when we're done, this points to all of our data and that points to nothing. It's left in a valid but unspecified state (in this example, pointing to nullptr), so that it can be safely destructed without damaging anything else, and certainly without damaging the original data.