Home > other >  Why is the copy constructor called in this example with std::vector?
Why is the copy constructor called in this example with std::vector?

Time:01-16

#include <iostream>
#include <vector>
using namespace std;

// Move Class
class Move {
private:
    // Declare the raw pointer as
    // the data member of class
    int* data;

public:

    // Constructor
    Move(int d)
    {
        // Declare object in the heap
        data = new int;
        *data = d;
        cout << "Constructor is called for "
            << d << endl;
    };

    // Copy Constructor
    Move(const Move& source)
        : Move{ *source.data }
    {
        cout << "Copy Constructor is called -"
            << "Deep copy for "
            << *source.data
            << endl;
    }

    // Move Constructor
    Move(Move&& source)
        : data{ source.data }
    {

        cout << "Move Constructor for "
            << *source.data << endl;
        source.data = nullptr;
    }

    // Destructor
    ~Move()
    {
        if (data != nullptr)
            cout << "Destructor is called for "
                << *data << endl;
        else
            cout << "Destructor is called"
                << " for nullptr "
                << endl;
        delete data;
    }
};

// Driver Code
int main()
{
    // Vector of Move Class
    vector<Move> vec;

    // Inserting Object of Move Class
    vec.push_back(Move{ 10 });
    vec.push_back(Move{ 20 });
    return 0;
}

output:
Constructor is called for 10
Move Constructor for 10
Destructor is called for nullptr

Constructor is called for 20
Move Constructor for 20
Constructor is called for 10
Copy Constructor is called -Deep copy for 10
Destructor is called for 10
Destructor is called for nullptr
Destructor is called for 10
Destructor is called for 20

CodePudding user response:

I think what you are asking is why is the copy constructor called instead of your move constructor? It is because your move constructor is not noexcept. If you mark it as no except, the move constructor will be used during reallocation instead.

See this related answer for more information: https://stackoverflow.com/a/47017784/6324364

The common recommendation is that move constructors should be noexcept if at all possible. Many standard library functions will not use the move constructor if it could throw an exception.

If your question is instead about the order of constructor calls - that is just dependent on the implementation. An example implementation of push_back could be:

// The callsite of this function is where "Constructor is called for 20"
template<typename T>
void push_back(T&& value) {
   // If the vector is full resize
   if (size() == capacity()) {
      auto* oldBuffer = m_buffer;
      // allocate larger amount of space,
      // increasing capacity but keeping size the same
      // ...

      // Here is your move constructor call
      m_buffer[size()] = std::move(value);

      // copy/move over old elements. This is your copy constructor
      // If your move constructor was noexcept, it would be used.
      // Otherwise the copy constructor is called.
      // ...

      m_size  ;
      
      // deallocate old buffer
      // ...
   } else {
      m_buffer[size() - 1] = std::move(value);
      m_size  ;
   }
}

The reason you seemingly get two constructor calls for 10 after the move constructor for 20 is because that is what your code does:

// Constructor
    Move(int d)
    {
        // Declare object in the heap
        data = new int;
        *data = d;
        cout << "Constructor is called for "
            << d << endl;
    };

    // Copy Constructor
    Move(const Move& source)
        // Here, your Copy constructor is calling the other constructor
        : Move{ *source.data }
    {
        cout << "Copy Constructor is called -"
            << "Deep copy for "
            << *source.data
            << endl;
    }

In your copy constructor you are explicitly calling your other constructor leading to the two log messages.

The implementation may do things in this order (move/copy new element, then move/copy old elements) as part of the strong exception guarantee provided by vector which says that the vector state will not be corrupted if copy/move of the new items fails. If it moved the old items first and then the new item constructor threw an exception, the vector state would be corrupted.

If you can add what you expected to happen in the code, maybe someone can give a better answer. As it stands, I don't fully understand what you are asking. You get those calls because that is how it is implemented.

CodePudding user response:

Vector is stored as a one piece in memory, continously. When you add the second element to the vector, its current memory needs to be expanded to accomodate the newly added element. This will require your already existing elements to be copied somewhere else where enough memory is allocated to accomodate both (in your case) the first and second element. Take a look at what reserve is doing and how you can benefit from it. Or, similarly, if your vector is of fixed size, you may also take a look at array.

  • Related