In The C++ programming language Edition 4 there is an example of a vector implementation, see relevant code at the end of the message.
uninitialized_move() initializes new T objects into the new memory area by moving them from the old memory area. Then it calls the destructor on the original T object, the moved-from object. Why is the destructor call necessary in this case?
Here is my incomplete understanding: moving an object means that the ownership of the resources owned by the moved-from object are transferred to the moved-to object. The remainings in the moved-from object are some possible members of built-in types that do not need to be destroyed, they will be deallocated when the the vector_base b goes out of scope (inside reserve(), after the swap() call). All pointers in the moved-from object are to be put to nullptr or some mechanism is employed to drop ownership of moved-from object on those resources so that we are safe, then why call the destructor on a depleted object when the "vector_base b" destructor will anyway deallocate the memory after the swap is done?
I understand the need to explicitly call the destructor in the cases when it must be called because we have something to destruct (e.g. drop elements) but I fail to see its meaning after the std::move + deallocation of vector_base. I read some texts on the net and I'm seeing the destructor call of the moved-from object as an signal (to whom or what?) that the lifetime of the object is over.
Please clarify to me what meaningful work remains to be done by the destructor? Thank you!
The code snippet below is from here http://www.stroustrup.com/4th_printing3.html
template<typename T, typename A>
void vector<T,A>::reserve(size_type newalloc)
{
if (newalloc<=capacity()) return; // never decrease allocation
vector_base<T,A> b {vb.alloc,size(),newalloc-size()}; // get new space
uninitialized_move(vb.elem,vb.elem+size(),b.elem); // move elements
swap(vb,b); // install new base
} // implicitly release old space
template<typename In, typename Out>
Out uninitialized_move(In b, In e, Out oo)
{
using T = Value_type<Out>; // assume suitably defined type function (_tour4.iteratortraits_, _meta.type.traits_)
for (; b!=e; ++b,++oo) {
new(static_cast<void*>(&*oo)) T{move(*b)}; // move construct
b->~T(); // destroy
}
return oo;
}
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.
In his proposal to implement move semantics through rvalue references, Howard Hinant considered the push for Destructive Move too. But as he pointed in the same article, a move operation basically deals with to objects (source an destination) simultaneously in transition state - even in single threaded applications. The problem with destructive move is that either source, or destination will experience a state in where derived subpart of an object is constructed while base subpart is destroyed or uninitialized. This was not possible prior to move proposal, and is not acceptable yet. So the other choice left was to leave the moved from object in a valid empty state and let the destructor remove the valid empty state. For many practical purposes, destroying valid empty state is a noop; But it's not possible to be generalized for all cases. So, the moved from object is supposed to be either destructed, or assigned(move or copy) to.
Related
In C++ move constructor is required to reset the moved object which to me seems to be a duplication of what the destructor does in most of the cases.
Is it correct that defining a reset-method and using it in both destructor and move-constructor is the best approach? Or maybe there are better ways?
Move constructors typically "steal" the resources held by the argument (e.g. pointers to dynamically-allocated objects, file descriptors, TCP sockets, I/O streams, running threads, etc.) rather than make copies of them, and leave the argument in some valid but otherwise indeterminate state. For some types, such as std::unique_ptr, the moved-from state is fully specified.
"Stolen" resources should not be released as this will usually lead to errors. For example, a move constructor which "steals" a pointer has to ensure that the destructor of the moved-from object won't delete the pointer. Otherwise, there will be a double-free. A common way of implementing this is to reset the moved-from pointer to nullptr.
Here is an example:
struct Pointer {
int *ptr;
// obtain a ptr resource which we will manage
Pointer(int* ptr) : ptr{ptr} {}
// steal another object's ptr resource, assign it to nullptr
Pointer(Pointer &&moveOf) : ptr{moveOf.ptr} {
moveOf.ptr = nullptr;
}
// make sure that we don't delete a stolen ptr
~Pointer() {
if (ptr != nullptr) {
delete ptr;
}
}
};
Is it correct that defining a reset-method and using it in both destructor and move-constructor is the best approach? Or maybe there are better ways?
This depends on the resource which is managed, but typically the destructor and move-constructor do different things. The move constructor steals the resource, the destructor frees a resource if it hasn't been stolen.
In C++ move constructor is required to reset the moved object which to me seems to be a duplication of what the destructor does in most of the cases.
You are right that there often is duplication of work. This is because C++ does not have destructive move semantics, so the destructor still gets called separately, even when an object has been moved from. In the example I have shown, ~Pointer() still needs to get called, even after a move. This comes with the runtime cost of checking whether ptr == nullptr. An example of a language with destructive move semantics would be Rust.
Related Posts:
Why does C++ move semantics leave the source constructed?
How does Rust provide move semantics?
How to define a move constructor?
is required to reset the moved object which to me seems to be a duplication of what the destructor does in most of the cases.
It seems to me that you misunderstand what a destructor is used for in most cases.
The purpose of the "reset" (as you call it) in move is to set the state of the object so that it satisfies the internal pre-conditions of the destructor (more generally, any class invariant). If the constructor didn't do that, then the object couldn't be destroyed, which would be against conventions and good practices and would likely lead to mistakes.
In many cases, the destructor cannot possibly do this same "reset". For example, there is no way to distinguish an invalid pointer from a valid pointer. This is why the move constructor of a smart pointer resets the pointer to null.
Is it correct that defining a reset-method and using it in both destructor and move-constructor is the best approach?
It is unclear when this could be useful. Doesn't seem typical.
I have this line here:
someSTLVector.push_back(SomeClass(...));
I was hoping what SomeClass would be constructed and moved to the back of the vector, without any copies. However, the destructor got called here. I tried amending this with an std::move:
someSTLVector.push_back(std::move(SomeClass(...)));
but the result didn't change.
I also tried defining the following in the SomeClass:
SomeClass(SomeClass&&) = default;
SomeClass& operator= (SomeClass&&) = default;
SomeClass(const SomeClass&) = delete;
SomeClass& operator= (const SomeClass&) = delete;
That didn't help either, the destructor was still called. Note that the SomeClass contains a reference as a member
It's apparent that SomeClass gets constructed and then copied into the vector. 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). SomeClass manages a resources which gets released in the destructor. 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.
How can I instantiate a class where the resulting object will be placed to the back of the vector, but not copied (and thus destroyed) in the process?
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.
An object that is moved is still destructed. So your SomeClass is probably moved into the vector (you could add a std::cout << message in your move constructor to verify) but it is then also destructed.
You can call std::vector::emplace_back to have the item constructed right into the vector. However, that still doesn't guarantee your object won't be moved (e.g. if the vector needs to grow it can allocate more space and then move all the objects to the new storage and then destroy them in their original location).
If you have some resource you're releasing in the destructor you need to ensure you don't do that release if the object was moved. Typically move constructors "empty" out the moved-from object (e.g. given SomeClass(SomeClass&& other) in that method you'd modify other to "empty it"). Then your destructor can see if it's "empty" (has been moved from) and not release whatever resources you're holding.
Have found comparable questions but not exactly with such a case.
Take the following code for example:
#include <iostream>
#include <string>
#include <vector>
struct Inner
{
int a, b;
};
struct Outer
{
Inner inner;
};
std::vector<Inner> vec;
int main()
{
Outer * an_outer = new Outer;
vec.push_back(std::move(an_outer->inner));
delete an_outer;
}
Is this safe? Even if those were polymorphic classes or ones with custom destructors?
My concern regards the instance of "Outer" which has a member variable "inner" moved away. From what I learned, moved things should not be touched anymore. However does that include the delete call that is applied to outer and would technically call delete on inner as well (and thus "touch" it)?
Neither std::move, nor move semantics more generally, have any effect on the object model. They don't stop objects from existing, nor prevent you from using those objects in the future.
What they do is ask to borrow encapsulated resources from the thing you're "moving from". For example, a vector, which directly only stores a pointer some dynamically-allocated data: the concept of ownership of that data can be "stolen" by simply copying that pointer then telling the vector to null the pointer and never have anything to do with that data again. It's yielded. The data belongs to you now. You have the last pointer to it that exists in the universe.
All of this is achieved simply by a bunch of hacks. The first is std::move, which just casts your vector expression to vector&&, so when you pass the result of it to a construction or assignment operation, the version that takes vector&& (the move constructor, or move-assignment operator) is triggered instead of the one that takes const vector&, and that version performs the steps necessary to do what I described in the previous paragraph.
(For other types that we make, we traditionally keep following that pattern, because that's how we can have nice things and persuade people to use our libraries.)
But then you can still use the vector! You can "touch" it. What exactly you can do with it is discoverable from the documentation for vector, and this extends to any other moveable type: the constraints emplaced on your usage of a moved-from object depend entirely on its type, and on the decisions made by the person who designed that type.
None of this has any impact on the lifetime of the vector. It still exists, it still takes memory, and it will still be destructed when the time comes. (In this particular example you can actually .clear() it and start again adding data to a new buffer.)
So, even if ints had any sort of concept of this (they don't; they encapsulate no indirectly-stored data, and own no resources; they have no constructors, so they also have no constructors taking int&&), the delete "touch"ing them would be entirely safe. And, more generally, none of this depends on whether the thing you've moved from is a member or not.
More generally, if you had a type T, and an object of that type, and you moved from it, and one of the constraints for T was that you couldn't delete it after moving from it, that would be a bug in T. That would be a serious mistake by the author of T. Your objects all need to be destructible. The mistake could manifest as a compilation failure or, more likely, undefined behaviour, depending on what exactly the bug was.
tl;dr: Yes, this is safe, for several reasons.
std::move is a cast to an rvalue-reference, which primarily changes which constructor/assignment operator overload is chosen. In your example the move-constructor is the default generated move-constructor, which just copies the ints over so nothing happens.
Whether or not this generally safe depends on the way your classes implement move construction/assignment. Assume for example that your class instead held a pointer. You would have to set that to nullptr in the moved-from class to avoid destroying the pointed-to data, if the moved-from class is destroyed.
Because just defining move-semantics is a custom way almost always leads to problems, the rule of five says that if you customize any of:
the copy constructor
the copy assignment operator
the move constructor
the move assignment operator
the destructor
you should usually customize all to ensure that they behave consistently with the expectations a caller would usually have for your class.
A a5(move(a1));
While after the move the member vars of a1 are set to defaults, a1 itself is not set to null. You can't do a1 == nullptr... to check if a1 is useless...
I find this odd. Is there something I'm misunderstanding here? I would think that if a1 is moved, it becomes useless, this should be indicated by setting it to null somehow. No?
The thing is that by leaving a1 in a non null state, it still can be used. There is no compiler warning or error. There is no real indication that the object is in a messed up state. if a has two member vars, an int and a dynamically alloc object, the dyn alloc object will point to nullptr but the int will have a def value (of course only if implemented right...easy to mess up).
So after the move you can
int number = a1.getInt();
and get back a number not realizing that a1 has been reset. In C and C++ we're taught to set pointers to null (a.k.a nullptr or NULL) when its resource is pilfered to eliminate such confusion. With the introduction of moving which pilfers resources of an object, is there no built in mechanism or best practice to indicate the object has been pilfered and thus left "reset" to default construction state?
EDIT
Added sample move c'tor
A(A&& other) : num(other.num), s(other.s){
other.num = 0;
other.s = nullptr; //dyn alloc obj
}
Why doesn't moving an object leave it null?
Because if the object is not a pointer, then it does not, in general, have a "null state", so you can't "leave it null"; and if it is a pointer you don't make it null when you "move" from it.
Leaving a1 in a non null state, it still can be used. There is no compiler warning or error. There is no real indication that the object is in a messed up state...
It's not in a messed-up state, it's in a valid state. But perhaps it's not a bad idea for there to be warning when you use the post-move value of a moved-from object.
The result of a move operation is supposed to leave the object in a valid but unspecified state. Typically you would move if you are going to destroy the object, or assign new values to the object. You would not normally attempt to use an object after moving out of it.
The move-constructor for a1's type is responsible for performing the move and leaving the object in the state that you wish. If the default-generated move constructor does not behave as you would like then you can write your own move constructor for the object. For example you could set a pointer to null in this move constructor. Or you could set a flag which indicates the object is in a moved-from state.
In your question the behaviour you "wish for" is more like the behaviour of swapping with an empty object. Perhaps the following code would suit you better:
A a5{};
using std::swap;
swap(a5, a1);
Regarding the update which suggests other.s points to a "dyn alloc obj": you should avoid doing this because it means you have to waste time writing a copy-constructor, move-constructor, copy-assignment operator, move-assignment operator, and destructor.
You are the architect of your own demise here: by introducing this pointer to your class you create the exact problem with moving that you are complaining about.
Instead any owned resource should be managed by its own class. The standard library's std::unique_ptr suffices for many such use cases.
I've been reading about move semantics in C++, and in the explanations people give a lot of analogies to help simplify it, and in my head all I can see is that what people call "moving" as opposed to "copying" is just a shallow copy of the object and setting any pointers in the "moved-from" object to null. Is this basically the gist? Shallow copy and set other's pointers to null?
Shallow copy and set other's pointers to null?
Shallow copy - yes. Setting other's pointers to null - not always.
The minimum requirement is that the moved-from object is in an "undefined but valid state", which is to say that you can reassign to it, move it again or delete it without causing the program to fail, but perform no other state-dependent operation on it. This means that it's perfectly valid in general to implement move-assignment in terms of std::swap.
Some objects define a stronger contract. One of these is std::unique_ptr. Moving-from one of these will result in it containing null, but this is explicitly documented.
Is move semantics just a shallow copy and setting other's pointers to null?
It can be, if the pointers being null satisfies the class invariant. That is to say: If the object with null pointers is a valid state.
So, I would give a longer description: Move constructor and assignment operator perform a shallow copy, and clean up the moved from object into a state that satisfies the class invariant.
Also remember that in the case of move assignment, you must remember to handle the pointer that would be overwritten by the swallow copy.
If the class owns pointed objects for example, the invariant requires that no two objects own the same object. There are at least three ways to implement this:
Set the pointer to null
Construct a new object
Swap the pointers with the moved to object (in case of move constructor, this is essentially same as one of the previous options since the pointer is initially uninitialized unless one of the previous is first performed, but with move assignment this handily takes care of the object previously pointed by the moved-to object).
It is sometimes necessary to also set non-resource data such as size field or similar to match the new state of the object if the invariant so requires. There are other resources that the object might hold and must also be cleaned up besides owning pointers to memory, for example file descriptors.