If std::vector reallocates objects to new memory by using move constructor then why does it have to call the destructor on the original objects?

Viewed 83

If the move constructor of your class is noexcept then std::vector will allocate new memory and then move construct your objects in the new memory. If it's not "noexcept" it will copy construct them. If it copy constructs them, then those objects still need to be destroyed before deallocating the old buffer. However why is it the case that if the object is moved it still calls the destructors on all the old objects. I don't see why this is necessary. Any destructor call on a moved-from object is not going to do anything, apart from some conditional checks. You could argue that the "object is technically not destroyed", and that's true, but since that memory is being deallocated, and the next time that memory is used the only defined way to access objects there is to construct them first, I don't see why this needs to be done:

struct Foo
{

    void* buffer;
    Foo() : buffer(new char[16]) {}
    Foo(Foo&& other) { buffer = other.buffer; if (other.buffer != nullptr) other.buffer = nullptr; }

    ~Foo()
    {
        if (buffer != nullptr) delete buffer;
    }
};

int main()
{

    

    Foo foo;
    Foo foo2 = std::move(foo);

    foo.~Foo(); /* NO EFFECT */

    /* BUT ASSUMING WE DIDN'T CALL THE CONSTRUCTOR, WE JUST CONSTRUCT OVER IT */
    new (&foo) Foo{};
    /* THEN THE OLD FOO CEASES TO EXIST EVEN IF THE DESTRUCTOR IS NEVER CALLED */

}

Here is a quick program showing that std::vector calls the destructors on the old moved-from objects:

#include <iostream>
struct Foo
{
    
    Foo() {}
    Foo(uint32 id) {  }
    Foo(const Foo& other) 
    {
        std::cout << "Copy constructor called\n";

    }
    Foo(Foo&& other) noexcept 
    {
    
        std::cout << "Move constructor called\n";
    };

    
    ~Foo()
    {
        std::cout << "Destructor called\n";
    }
};

int main()
{
    Foo foo;
    std::vector<Foo> v;
    v.push_back(std::move(foo));
    v.push_back(std::move(foo));
    v.push_back(std::move(foo));
    v.push_back(std::move(foo));

}
1 Answers

To end an object's lifetime, its destructor must be called. Also, what happens when an object it moved is up to the implementation of the class. You have two objects and the move constructor is allowed to move resources around how it sees fit.

An example of a simple string class which, when moving, leaves the moved-from object in a valid state:

class string {
public:
    string(const char* s) : m_len(std::strlen(s)), m_data(new char[m_len + 1]) {
        std::copy_n(s, m_len + 1, m_data);
    }
    string(string&& rhs) :
        m_len(std::exchange(rhs.m_len, 0)),
        m_data(std::exchange(rhs.m_data, new char[1]{})) 
    {}
    ~string() {
        delete[] m_data;
    }

private:
    size_t m_len;
    char* m_data;
};

Without calling the destructor of the moved-from object, it would leak. An implementation like above is not uncommon. Just because an object has been move-from, it doesn't mean that it's void of resources to free.

Related