Why Is the Destructor Call After the Std::Move Necessary

why is the destructor call after the std::move necessary?

Moving from an object just means that the moved-from object might donate its guts to live on in another live object shortly before it is [probably] going to die. Note, however, that just because an object donated its guts that the object isn't dead! In fact, it may be revived by another donating object and live on that object's guts.

Also, it is important to understand that move construction or move assignment can actually be copies! In fact, they will be copies if the type being moved happens to be a pre-C++11 type with a copy constructor or a copy assignment. Even if a class has a move constructor or a move assignment it may choose that it can't move its guts to the new object, e.g., because the allocators mismatch.

In any case, a moved from object may still have resources or need to record statistics or whatever. To get rid of the object it needs to be destroyed. Depending on the class's contracts it may even have a defined state after being moved from and could be put to new use without any further ado.

Move semantics: why is the destructor called on the moved instance and is that a problem?

is that (deleting nullptr) even a valid operation (i.e. does that result in UB; will it eventually crash my application)?

Deleting nullptr is a no-op. It is valid. As per the online CPP reference:

If expression evaluates to a null pointer value, no destructors are called, and the deallocation function is not called.

I believe your move constructor and move assignment operator are incorrect. Why use raw pointers anyway?

If you are catching up with modern C++ (as you mentioned), you should be using smart pointers.

Why does std::vector call the destructor while leaving a different scope?

paths.push_back(*path);

this does a few things.

  1. It constructs a Path copy from the heap-allocated *path object.

  2. It resizes the paths vector. This can sometimes involve just increasing an integer, but at other times it involves moving every existing Path object and destroying the old ones.

In the first point, you have a leak. new allocates objects on the free store, you are always responsible for ensuring they are cleaned up. Copying the object off the free store into a vector does not clean up the object on the free store.

In the second point, vector actually holds actual objects, not references to them. So when you resize the buffer (which push_back can do), you have to move the values of the objects around, then clean up the ones you are discarding.

That cleanup is the destructor you are doing.

You appear to be a C# or Java programmer. In both of those languages, actual values of objects are really hard to create -- they want to hold onto garbage collected references to objects. An array of objects is actually an array of references to objects in those languages. In C++, a vector of objects is a vector actually containing the objects in question.

Your use of new is also a tip. There is no need for new there, nor is there need for a pointer:

std::vector<cuarl_path::Path> RoverPlanner::get_paths(const char *filename) const
{
pugi::xml_document doc;
doc.load_file(filename);
pugi::xml_node root = doc.document_element();

std::vector<cuarl_path::Path> paths;
for (auto&& path_node : root.children()) {
std::vector<cuarl_path::Segment*> segments;
for (auto segment_node : path_node.children())
{
//do stuff to populate the `segments` vector
}

cuarl_path::Path path = cuarl_path::Path(segments);
paths.push_back(std::move(path)); //Path destructor called here
}

return paths;
}

you'll still get path destructors called on the line (in fact, you'll get an extra one), but your code won't leak. Assuming your move constructor is correct (and in fact moves the state of the path properly), everything should work.

Now a more efficient version looks like:

std::vector<cuarl_path::Path> RoverPlanner::get_paths(const char *filename) const
{
pugi::xml_document doc;
doc.load_file(filename);
pugi::xml_node root = doc.document_element();

std::vector<cuarl_path::Path> paths;
auto&& children = root.children();
paths.reserve(children.size());
for (auto path_node : children) {
std::vector<cuarl_path::Segment*> segments;
for (auto segment_node : path_node.children())
{
//do stuff to populate the `segments` vector
}

paths.emplace_back(std::move(segments));
}

return paths;
}

which gets rid of all of the temporary variables you are messing with, and moves resources when they are of no more use.

Assuming efficient move constructors, the big gains here are that we pre-researve the paths vector (saving lg(n) memory allocations) and we move the segments vector into the constructor of a path (if written right, that could avoid a needless copy of the buffer of segment pointers).

This version also has no destructors called on the line in question, but I don't consider that to be particularly important; the cost of a destructor of an empty path should be nearly free, and even plausible to optimize away.

I also possibly avoided a copy of the path_node object, which could be worth avoiding, depending on how it is written.

std::move calls the destructor unexpectedly

std::move(lvalue or xvalue) does not call a destructor.

It just changes the passed reference to an rvalue-reference, so move-semantics apply.

So, why is your local destroyed too early?

Simple, returning a reference to a local variable is UB:

Can a local variable's memory be accessed outside its scope?

Going over your named constructors one-by-one:

  1. namedConstructor1 returns an rvalue-reference.

    But you try to return a local (which is an lvalue), and the compiler complains about that error.
  2. namedConstructor2 is in principle the same as namedConstructor1, but you added an explicit cast (in the form of std::move) from lvalue to rvalue-reference, which shuts the compiler up.

    Thus, you get UB for lying to the compiler, specifically the locals lifetime ends at the end of the function, before the returned rvalue-reference is used.
  3. namedConstructor3 is ok, though sub-optimal.

    You are using the rvalue-reference to initialize the return-value (which is not a reference).

    The sub-optimal part is due to that std::move disabling return-value-optimization, which would obviate the need for the implementation to actually move (or copy) in this case.

At which point of destructor call the object ceases to exist?

At which point of destructor call the object ceases to exist?

The lifetime of the object is ended by the call to its destructor. Within the destructor body, the sub-objects are still alive and member functions may be called. After the destructor body, the sub objects are destroyed.

if it's legal to call an object's function inside its destructor or not

It is legal.

Note however that calling a virtual function works differently than one might expect.

Why is the destructor called upon construction of an object in std::vector::push_back(T object) method?

I was hoping what SomeClass would be constructed and moved to the back of the vector, without any copies.

That is what does happen.

However, the destructor got called here.

Indeed. That would be the temporary object that you passed to the move constructor:

someSTLVector.push_back(SomeClass(...));
^^^^^^^^^^^^^^

That there is syntax for initialisation of a temporary object.

It's apparent that SomeClass gets constructed and then copied into the vector.

Well, moved to be exact. Although moving is a form of copying.

I want't to avoid that and have it be constructed as a part of the vector (or at least moved to the vector, avoiding any copying).

You've already managed to avoid the copying. To avoid the move, you can use the emplace_back member function instead of push_back:

someSTLVector.emplace_back(...);

This forwards the arguments directly to the constructor of the element.


If the destructor is called when copying the object, the resource is released and the object becomes invalid, pointing to a resource that no longer exists.

If your destructor releases some resources, then the defaulted move constructor / assignment are probably not doing what you would want them to do. See rule of five / three.

Move constructor not calling destructor?

My understanding is that the destructor is called on the input after the call to the move constructor

Your understanding is incorrect. Nothing about move semantics or C++11 changes when destructors get called. Nothing has changed here - foo's destructor gets called at the end of main in this case.

Your confusion probably stems from the fact that in many cases it appears as though the destructor is called immediately after the move constructor.

For example, if you were to create a Bar with an rvalue: Bar bar(Foo{}); the destructor of Foo would run right after Bar's move constructor. This isn't because there's a move constructor though - it's because the lifetime of the temporary Foo object has ended.

Why destructor of a moved from object is invoked?

Why the destructor of a moved object is called twice?

The first destructor destroys the moved-from tmp when it goes out of scope at the first } in main(). The second destructor destroys the move-constructed foo that you push_back'd into v at the end of main() when v goes out of scope.

What is moved from the object that is being moved really?

The compiler-generated move constructor move-constructs id, which is a std::string. A move constructor of std::string typically takes ownership of the block of memory in the moved-from object storing the actual string, and sets the moved-from object to a valid but unspecified state (in practice, likely an empty string).

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?

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.

When is the destructor of an object that is being moved to called in C++?

In the first version the object gets destroyed for the first time as part of returning the object constructed in getSomeA. Its return statement effectively constructs a temporary object, which then gets assigned to main's b1. If you ignore the process of returning from a function, the sequence of events is:

A <temporary object>(std::bind( ... )

b1=<temporary object>

<temporary object gets destroyed>

At this point the bound function gets called, as a result of the temporary object gets destroyed. The temporary object is a fully tricked out object, with all the rights and privileged granted thereof. Including a destructor.

But wait, there's more! b1 is a perfect bit-by-bit copy of the original object, with the bound function, before it got destroyed. So when b1 gets destroyed, the function gets called again.

This is the indirect consequence of the Rule Of Three. When an object owns a resource, and must maintain an exclusive ownership of the resource you will need to provide a copy and/or move constructor and and assignment operator to spell out exactly what should happen in that situation.

P.S. these rules change slightly with C++17's guaranteed copy elision, but the underlying concept is still the same.



Related Topics



Leave a reply



Submit