I have already asked a similar question a while ago, but I'm still unclear on some details.
Under what circumstances is the postblit constructor called?
What are the semantics of moving an object? Will it be postblitted and/or destructed?
What happens if I return a local variable by value? Will it implicitly be moved?
How do I cast an expression to an rvalue? For example, how would a generic swap look like?
A postblit constructor is called whenever the struct is copied - e.g. when passing a struct to a function.
A move is a bitwise copy. The postblit constructor is never called. The destructor is never called. The bits are simply copied. The original was "moved" and so nothing needs to be created or destroyed.
It will be moved. This is the prime example of a move.
There are a number of different situations that a swap function would have to worry about if you want to make it as efficient as possible. I would advise simply using the swap function in std.algorithm. The classic swap would result in copying and would thus call the postblit constructor and the destructor. Moves are generally done by the compiler, not the programmer. However, looking at the official implementation of swap, it looks like it plays some tricks to get move semantics out of the deal where it can. Regardless, moves are generally done by the compiler. They're an optimization that it will do where it knows that it can (RVO being the classic case where it can).
According to TDPL (p. 251), there are only 2 cases where D guarantees that a move will take place:
All anonymous rvalues are moved, not copied. A call to this(this)
is never inserted when the source is an anonymous rvalue (i.e., a
temporary as featured in the function hun above).
All named temporaries that are stack-allocated inside a function and
then returned elide a call to this(this).
There is no guarantee that other potential elisions are observed.
So, the compiler may use moves elsewhere, but there's no guarantee that it will.
As far as I understand:
1) When a struct is copied, as opposed to moved or constructed.
2) The point of move semantics is that neither of the two needs to happen. The new location of the struct is initialized with a bit-wise copy of the struct, and the old location goes out of scope and becomes inaccessible. Thus, the struct has "moved" from A to B.
3) That is the typical move situation:
S init(bool someFlag)
{
S s;
s.foo = someFlag? bar : baz;
return s; // `s` can now be safely moved from here...
}
// call-site:
S s = init(flag);
//^ ... to here.
Related
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.
struct big_struct{
vector<int> a_vector;
map<string, int> a_map;
};
big_struct make_data(){
big_struct return_this;
// do stuff, build that data, etc
return return_this;
}
int main(){
auto data = make_data();
}
I have seen move semantics applied to constructors, but in this bit of code, I'm wondering if the big struct is copied entirely when returned or not. I'm not even sure it is related to move semantics. Does C++ always copies this kind of data, or is it optimized? Could this code be change or improved?
What about a function that returns a vector or a map? Is that map/vector copied?
You don't need to change anything. What you have right now is the rule of zero. Since both std::map and std::vector are moveable your class automatically gets move operations added to it.
Since return_this is a function local object it will be treated as an rvalue and it will either be moved for you or NRVO will kick in and no move or copy will happen.
Your code will either produce a default construction call for return_this and a move constructor call for data or you will see a single default constructor call for data (NRVO makes return_this and data the same thing).
As stated here, your class actually has a move-constructor (implicitly generated one), so it shouldn't be copied in your code, at least once (in main).
One problem is, what you're relying upon is called NRVO, and compilers are not required to implement it (unlike its happier simpler brother, RVO.) So your struct has a chance, quite very small, to be copied in the return statement—but so small that return-by-move (like return std::move(return_this);) is never actually recommended. Chances are quite high the NRVO will actually be applied if you really have a single return statement in your function that returns a single named object.
Using std::list supporting move semantics as an example.
std::list<std::string> X;
... //X is used in various ways
X=std::list<std::string>({"foo","bar","dead","beef"});
The most straightforward way for compiler to do the assignment since C++11 is:
destroy X
construct std::list
move std::list to X
Now, compiler isn't allowed to do following instead:
destroy X
contruct std::list in-place
because while this obviously saves another memcpy it eliminates assignment. What is the convenient way of making second behaviour possible and usable? Is it planned in future versions of C++?
My guess is that C++ still does not offer that except with writing:
X.~X();
new(&X) std::list<std::string>({"foo","bar","dead","beef"});
Am I right?
You can actually do it by defining operator= to take an initializer list.
For std::list, just call
X = {"foo","bar","dead","beef"}.
In your case, what was happening is actually:
Construct a temporary
Call move assignment operator on X with the temporary
On most objects, such as std::list, this won't actually be expensive compared to simply constructing an object.
However, it still incurs additional allocations for the internal storage of the second std::list, which could be avoided: we could reuse the internal storage already allocated for X if possible. What is happenning is:
Construct: the temporary allocates some space for the elements
Move: the pointer is moved to X; the space used by X before is freed
Some objects overload the assignment operator to take an initializer list, and it is the case for std::vector and std::list. Such an operator may use the storage already allocated internally, which is the most effective solution here.
// Please insert the usual rambling about premature optimization here
Is it planned in future versions of C++?
No. And thank goodness for that.
Assignment is not the same as destroy-then-create. X is not destroyed in your assignment example. X is a live object; the contents of X may be destroyed, but X itself never is. And nor should it be.
If you want to destroy X, then you have that ability, using the explicit-destructor-and-placement-new. Though thanks to the possibility of const members, you'll also need to launder the pointer to the object if you want to be safe. But assignment should never be considered equivalent to that.
If efficiency is your concern, it's much better to use the assign member function. By using assign, the X has the opportunity to reuse the existing allocations. And this would almost certainly make it faster than your "destroy-plus-construct" version. The cost of moving a linked list into another object is trivial; the cost of having to destroy all of those allocations only to allocate them again is not.
This is especially important for std::list, as it has a lot of allocations.
Worst-case scenario, assign will be no less efficient than whatever else you could come up with from outside the class. And best-case, it will be far better.
When you have a statement involving a move assignment:
x = std::move(y);
The destructor is not called for x before doing the move. However, after the move, at some point the destructor will be called for y. The idea behind a move assignment operator is that it might be able to move the contents of y to x in a simple way (for example, copying a pointer to y's storage into x). It also has to ensure its previous contents are destroyed properly (it may opt to swap this with y, because you know y may not be used anymore, and that y's destructor will be called).
If the move assignment is inlined, the compiler might be able to deduce that all the operations necessary for moving storage from y to x are just equivalent to in-place construction.
Re your final question
” Am I right?
No.
Your ideas about what's permitted or not, are wrong. The compiler is permitted to substitute any optimization as long as it preserves the observable effects. This is called the "as if" rule. Possible optimizations include removing all of that code, if it does not affect anything observable. In particular, your "isn't allowed" for the second example is completely false, and the reasoning "it eliminates assignment" applies also to your first example, where you draw the opposite conclusion, i.e. there's a self-contradiction right there.
In C++11, "move semantics" was introduced, implemented via the two special members: move constructor and move assignment. Both of these operations leave the moved-from object constructed.
Wouldn't it have been better to leave the source in a destructed state? Isn't the only thing you can do with a moved-from object is destruct it anyway?
In the "universe of move operations" there are four possibilities:
target source
is is left
----------------------------------------------------------
constructed <-- constructed // C++11 -- move construction
constructed <-- destructed
assigned <-- constructed // C++11 -- move assignment
assigned <-- destructed
Each of these operations is useful! std::vector<T>::insert alone could make use of the first three. Though note:
The source of the 2nd and 4th should not have automatic, static or thread storage duration. The storage duration of the source must be dynamic, else the compiler will call the destructor on the already destructed object. And no, the compiler can not (in general) track if an object has been moved-from:
X x1, x2;
if (sometimes)
{
x1 = std::move(x2);
}
// Is x2 moved-from here?
The 2nd and 4th can be emulated by the 1st and 3rd respectively, by simply manually calling the destructor on the source after the operation.
The 1st and 3rd are crucial. Algorithms such as std::swap and std::sort regularly need both of these operations. These algorithms do not need to destruct any of their input objects — only change their values.
Armed with this knowledge, in the 2001-2002 time frame I focused my efforts on the two operations that left their source constructed because these two operations would have the largest (positive) impact on what was then C++98. I knew at the time that if I did not curtail the ambitions of this project, that it would never succeed. Even curtailed, it was borderline too-ambitious to succeed.
This curtailment is acknowledged in the original move semantics proposal under the section titled "Destructive move semantics".
In the end, we simply gave up on this as too much pain for not
enough gain. However the current proposal does not prohibit
destructive move semantics in the future. It could be done in
addition to the non-destructive move semantics outlined in this
proposal should someone wish to carry that torch.
For more details on what one can do with a moved-from object, see https://stackoverflow.com/a/7028318/576911
How many copies happen/object exist in the following, assuming that normal compiler optimizations are enabled:
std::vector<MyClass> v;
v.push_back(MyClass());
If it is not exactly 1 object creation and 0 copying, What can I do (including changes in MyClass) to achieve that, since it seems to me that that is all that should really be necessary?
If the constructor of MyClass has side-effects, then in C++03 the copy is not permitted to be elided. That's because the temporary object that's the source of the copy has been bound to a reference (the parameter of push_back).
If the copy constructor of MyClass has no side-effects then the compiler is permitted to optimize it away under the "as-if" rule. I think the only sensible way to determine whether it actually has done so with "normal optimizations" is to inspect the emitted code. Different people have different ideas what's normal, and a given compiler might be sensitive to the details of MyClass. My guess is that what this amounts to is whether or not the compiler (or linker) inlines everything in sight. If it does then it will probably optimize, if it doesn't then it won't. So even the size of the constructor code might be relevant, never mind what it does.
So I think the main thing you can do is to ensure that both the default and the copy constructor of MyClass have no side-effects and are available to be inlined. If they're not available then of course the compiler will assume that they could have side-effects and will do the copy. If link-time optimization is a normal compiler option for you, then you don't have to do much to make them available. Otherwise, if they're user-defined then do it in the header file that defines MyClass. You might be able to get away with the default constructor having certain kinds of side-effects: if the effects don't depend on the address of the temporary being different from the address of the vector element then "as-if" still applies.
In C++11 you have a move (that likewise must not be elided if it has side-effects), but you can use v.emplace_back() to avoid that. The move would call the move constructor of MyClass if it has one, otherwise the copy constructor, and everything I say above about "as-if" applies to moves. emplace_back() calls the no-args constructor to construct the vector element (or if you pass arguments to emplace_back then whatever constructor matches those args), which I think is exactly what you want.
You mean:
std::vector<MyClass> v;
v.push_back(MyClass());
None. The temporary will cause the move version of push_back to be called. Even the move construction will most likely be elided.
If you have a C++11 compiler, you can use emplace_back to construct the element at the end of the vector, zero copies necessary.
In C++03, you would have a construction and a copy, plus destruction of the temporary.
If your compiler supports C++11 and MyClass defines a move constructor, then you have one construction and a move.
As mentionned by Timbo, you can also use emplace_back to avoid the move, the object being constructed in-place.