Given the following code:
struct obj {
int i;
obj() : i(1) {}
obj(obj &&other) : i(other.i) {}
};
void f() {
obj o2(obj(obj(obj{})));
}
I expect release builds to only really create one object and never call a move constructor because the result is the same as if my code was executed. Most code is not that simple though, I can think of a few hard to predict side effects that could stop the optimizer from proving the "as if":
changes to global or "outside" things in either the move constructor or destructor.
potential exceptions in the move constructor or destructor (probably bad design anyway)
internal counting or caching mechanisms changing.
Since I don't use any of these often can I expect most of my moves in and out of functions which are later inlined to be optimized away or am I forgetting something?
P.S. I know that just because an optimization is possible does not mean it will be made by any given compiler.
This doesn't really have anything to do with the as-if rule. The compiler is allowed to elide moves and copies even if they have some side effect. It is the single optimization that a compiler is allowed to do that might change the result of your program. From §12.8/31:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects.
So the compiler doesn't have to bother inspecting what happens inside your move constructor, it will likely get rid of any moves here anyway. To demonstrate this, consider the following example:
#include <iostream>
struct bad_mover
{
static int move_count;
bad_mover() = default;
bad_mover(bad_mover&& other) { move_count++; }
};
int bad_mover::move_count = 0;
int main(int argc, const char* argv[])
{
bad_mover b{bad_mover(bad_mover(bad_mover()))};
std::cout << "Move count: " << bad_mover::move_count << std::endl;
return 0;
}
Compiled with g++ -std=c++0x:
Move count: 0
Compiled with g++ -std=c++0x -fno-elide-constructors:
Move count: 3
However, I would question any reason you have for providing a move constructor that has additional side effects. The idea in allowing this optimization regardless of side effects is that a copy or move constructor shouldn't do anything other than copy or move. The program with the copy or move should be exactly the same as without.
Nonetheless, your calls to std::move are unnecessary. std::move is used to change an lvalue expression to an rvalue expression, but an expression that creates a temporary object is already an rvalue expression.
Using std::move( tmp(...) ) is completely pointless, the temporary tmp is already an rvalue, you don't need to use std::move to cast it to an rvalue.
Read this series of articles: Want Speed? Pass By Value
You'll learn more and understand better than you will by asking a question on Stackoverflow
Related
I tested this code to see that the compiler automatically transfers temporary object to variables without needing a move constructor.
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"Hi from default\n";
}
A(A && obj)
{
cout<<"Hi from move\n";
}
};
A getA()
{
A obj;
cout<<"from getA\n";
return obj;
}
int main()
{
A b(getA());
return 0;
}
This code prints "Hi from default from getA" and not the presumed "Hi from move"
In optimization terms, it's great. But, how to force the call to the move constructor without adding a copy? (if I wanted a specific behavior for my temporary objects)
Complementary question: I though that if I did not write a move constructor there would be a copy each time I would assign a rvalue to a lvalue (like in this code at the line A b(getA());). Since it's not the case and the compiler seems to do well, when is it really useful to implement the move semantics?
In optimisation terms, it's great. But, how to force the call to the move constructor without adding a copy ? (if I wanted a specific behavior for my temporary objects)
Normally this requires disabling an optimization flag in order to get this behavior. For gcc and clang you can use -fno-elide-constructors to turn off copy elison. For MSVS it will not do it in debug mode(optimizations turned off) but I'm not sure if they have a specific flag for this
You can also call std::move in the return statement which will disable the elision and force the compiler to generate the temporary and move from it.
return std::move(obj);
That said, RVO/NRVO is something that you should want. You shouldn't want to even have the temporary created if you can help it as less work done means more work you can do in the same time. To that end C++17 introduces guaranteed copy elision which stops those temporaries from even existing.
This doesn't mean you should not write a move constructor if you can (well if you follow the rule of zero then you would not write one and just use the compiler provided one). There may be times where the compiler cannot elide a temporary or you want to move an lvalue so it is still a useful thing to have.
As this wiki page says (code exerted as below), return value optimization is an allowed by C++ compiler, but still depends on the implementation. To reduce the cost of copying, is it recommended to do optimize it manually (assign the object of function to a reference, like const C& obj = f();) or leave the compiler to do such optimization in practice?
#include <iostream>
struct C {
C() {}
C(const C&) { std::cout << "A copy was made.\n"; }
};
C f() {
return C();
}
int main() {
std::cout << "Hello World!\n";
C obj = f();
}
EDIT: Update the change as const reference.
You can't (portably) use the temporary return value to initialise a non-const reference, so that's certainly not recommended.
Using it to initialise a const reference wouldn't have any effect on whether or not the copy/move of the return expression's value might be elided; although it would eliminate the notional copy/move used to initialise the variable from the returned value, whether or not that might have been elided. Of course, that's not the same as initialising a (non-reference) variable, since you can't modify it.
In practice, any compiler with a decent optimiser will elide copies and moves wherever it's allowed to. If you're not using a decent optimiser, then you can't expect decent performance anyway.
To manually make sure you don't get any redundant copies of objects, you need to do a bit more than what you did so far. Earlier I answered that it wouldn't be possible, but I was wrong. Also, your use of const& may disallow certain operations on the returned value that you do want to allow. Here's what I would do if you need to do the optimisations manually:
#include <iostream>
struct S {
S()
{ std::cout << "default constructor\n"; }
S(const S &)
{ std::cout << "copy constructor\n"; }
S(S &&)
{ std::cout << "move constructor\n"; }
~S()
{ std::cout << "destructor\n"; }
};
S f() { return {}; }
int main() {
auto&&s = f();
std::cout << "main\n";
}
This prints "default constructor", followed by "main", and then "destructor". This is the output regardless of whether any copy elision takes place. Inside main, s is a named reference, so it is an lvalue, and it is not const-qualified. You can do everything with it that you otherwise could.
Given that it turns out fairly easy to avoid relying on copy elision in cases such as these, as long as you take care to pay attention to it from the start, it may be worth your efforts if you have to worry about other compilers not performing copy elision. Most compilers are capable of that, and there is a fair chance that if a compiler doesn't, it will have other, bigger, problems anyway, so there is a good argument for not worrying about it.
However, at the same time, copy elision is somewhat unreliable: even current optimising compilers do not always perform it, simply because there may be corner cases where copy elision would make sense, but is not permitted by the standard or not possible for that particular implementation. Forcing yourself to write code that doesn't rely on copy elision means you cannot get stuck in that situation.
That said, there are still some cases where copy elision can only realistically be eliminated by optimising compilers, so you may have no choice but to rely on it:
Suppose we add void m(); to S's definition. Suppose we now edit f to
S f() {
S s;
s.m();
return s;
}
This is more difficult to rewrite into a form that guarantees no redundant copies. Yet at the same time, copies are unnecessary, as can easily be determined from the fact that with GCC (and probably other compilers too), by default, no copies are made.
My final conclusion is that it's probably not worth optimising for compilers that don't perform RVO, but it is worth thinking carefully about what exactly makes it work, and writing code in such a way that RVO remains not only possible, but becomes something a compiler is very likely to do.
This question already has answers here:
What are copy elision and return value optimization?
(5 answers)
Closed 8 years ago.
As a C++ newbie I really have problems understanding the new Move-Constructor of C++11 and I hope someone can explain a specific situation I stumbled upon. Let's take this example code:
#include <iostream>
using namespace std;
class Model {
public:
int data;
Model(int data) : data(data) { cout << "Constructor" << endl; }
Model(Model&& model) { cout << "Move constructor" << endl; }
~Model() { cout << "Destructor" << endl; }
private:
Model(const Model& model);
const Model& operator=(const Model&);
};
Model createModel(int data) {
return Model(data);
}
int main(void) {
Model model = createModel(1);
cout << model.data << endl;
return 0;
}
So I have created a createModel function which should return a model as a temporary rvalue and I want to assign it to an lvalue. I don't want the compiler to create copies of the Model object so I define the copy constructor as private and I do the same with the assignment operator to make sure no data is copied. After doing this the code correctly no longer compiles so I added the Move constructor and now it compiles again. But when I run the program I get this output:
Constructor
1
Destructor
So the Move Constructor was never called. I don't understand why I have to specify the move constructor to be able to compile the program when it is not used at all during runtime.
Is it because the compiler (GCC 4.8.2) optimizes the Move Constructor away? Or is there some other magic performed here?
So can someone please explain what exactly happens in the code above? The code does what I want but I really don't understand why.
There are two moves that could happen in your program:
From the function to the return object.
From the return object to model.
Both of these moves can be elided by the compiler for the same reason:
when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
There are other situations in which copy/move elision occurs (see §12.8/31 in C++11). Note that copy/move elision is entirely optional - the compiler doesn't have to do it.
Note that the compiler is allowed to optimize absolutely anything away as long as it doesn't change the behaviour of your program (under the as-if rule). The reason that copy/move elision is explicitly mentioned in the standard is because it might change the behaviour of your program if your copy/move constructors have side effects. The compiler is allowed to perform this optimization even if it changes the behaviour of your program. This is why your copy/move constructors should never have side effects, because then your program will have multiple valid execution paths.
You can pass the -fno-elide-constructors option to gcc to ensure that this optimization is never performed.
i think this is what you call copy elision (i.e. prevent the copy of an object) and directly use it. See: copy elision: move constructor not called when using ternary expression in return statement?
You are a "victim" of Return Value Optimization here.
Note this: "In C++, it is particularly notable for being allowed to change the observable behaviour of the resulting program".
EDIT: Hence, the compiler is allowed to apply the optimization, even though the side effects of the move-ctor (cout) have been changed.
From the standard definition of copy elision method:
In C++ computer programming, copy elision refers to a compiler optimization technique that eliminates unnecessary copying of objects.
Let us consider following code:
#include <cstdlib>
#include <iostream>
using namespace std;
int n=0;
struct C
{
C (int) {}
C(const C&) {++n;}
};
int main(int argc, char *argv[])
{
C c1(42);
C c2=42;
return n;
}
This line "return n" will returns either 0 or 1, depending on whether the copy was elided.
Also consider this code:
#include <iostream>
struct C {
C() {}
C(const C&) { std::cout << "Hello World!\n"; }
};
void f() {
C c;
throw c; // copying the named object c into the exception object.
} // It is unclear whether this copy may be elided.
int main() {
try {
f();
}
catch(C c) {
}
}
It says that
// copying the exception object into the temporary in the exception declaration.
//It is also unclear whether this copy may be elided.
So my question is how useful is implement such optimization method, if sometimes results are undefined? And in general how often it is used?
The important bit is that the standard explicitly allows for this, and that means that you cannot assume that the side effects of a copy constructor will be executed as the copies might be elided. The standard requires that the implementation of a copy-constructor has copy-constructor semantics, that is, has as whole purpose the generation of a second object semantically equivalent in your domain to the original object. If your program complies with that, then the optimization will not affect the program.
It is true, on the other hand, that this is the only situation I can think where the standard allows for different visible outcomes from the same program depending on what the compiler does, but you have been advised that you should not have side effects in your copy constructor (or rather, you cannot depend on the exact number of copies performed).
As to whether it is worth it, yes it is. In many cases copies are quite expensive (I am intentionally ignoring move-constructors in C++11 from the discussion). Consider a function that returns a vector<int>, if the copy is not elided, then another dynamic allocation is required, copy of all of the vector contents and then release of the original block of memory all three operations can be expensive.
Alternatively, you could force users to change their code to create an empty object and pass it by reference, but that will make code harder to read.
I was reading the difference between direct-initialization and copy-initialization (§8.5/12):
T x(a); //direct-initialization
T y = a; //copy-initialization
What I understand from reading about copy-initialization is that it needs accessible & non-explicit copy-constructor, or else the program wouldn't compile. I verified it by writing the following code:
struct A
{
int i;
A(int i) : i(i) { std::cout << " A(int i)" << std::endl; }
private:
A(const A &a) { std::cout << " A(const A &)" << std::endl; }
};
int main() {
A a = 10; //error - copy-ctor is private!
}
GCC gives an error (ideone) saying:
prog.cpp:8: error: ‘A::A(const A&)’ is private
So far everything is fine, reaffirming what Herb Sutter says,
Copy initialization means the object is initialized using the copy constructor, after first calling a user-defined conversion if necessary, and is equivalent to the form "T t = u;":
After that I made the copy-ctor accessible by commenting the private keyword. Now, naturally I would expect the following to get printed:
A(const A&)
But to my surprise, it prints this instead (ideone):
A(int i)
Why?
Alright, I understand that first a temporary object of type A is created out of 10 which is int type, by using A(int i), applying the conversion rule as its needed here (§8.5/14), and then it was supposed to call copy-ctor to initialize a. But it didn't. Why?
If an implementation is permitted to eliminate the need to call copy-constructor (§8.5/14), then why is it not accepting the code when the copy-constructor is declared private? After all, its not calling it. Its like a spoiled kid who first irritatingly asks for a specific toy, and when you give him one, the specific one, he throws it away, behind your back. :|
Could this behavior be dangerous? I mean, I might do some other useful thing in the copy-ctor, but if it doesn't call it, then does it not alter the behavior of the program?
Are you asking why the compiler does the access check? 12.8/14 in C++03:
A program is ill-formed if the copy
constructor or the copy assignment
operator for an object is implicitly
used and the special member function
is not accessible
When the implementation "omits the copy construction" (permitted by 12.8/15), I don't believe this means that the copy ctor is no longer "implicitly used", it just isn't executed.
Or are you asking why the standard says that? If copy elision were an exception to this rule about the access check, your program would be well-formed in implementations that successfully perform the elision, but ill-formed in implementations that don't.
I'm pretty sure the authors would consider this a Bad Thing. Certainly it's easier to write portable code this way -- the compiler tells you if you write code that attempts to copy a non-copyable object, even if the copy happens to be elided in your implementation. I suspect that it could also inconvenience implementers to figure out whether the optimization will be successful before checking access (or to defer the access check until after the optimization is attempted), although I have no idea whether that warranted consideration.
Could this behavior be dangerous? I
mean, I might do some other useful
thing in the copy-ctor, but if it
doesn't call it, then does it not
alter the behavior of the program?
Of course it could be dangerous - side-effects in copy constructors occur if and only if the object is actually copied, and you should design them accordingly: the standard says copies can be elided, so don't put code in a copy constructor unless you're happy for it to be elided under the conditions defined in 12.8/15:
MyObject(const MyObject &other) {
std::cout << "copy " << (void*)(&other) << " to " << (void*)this << "\n"; // OK
std::cout << "object returned from function\n"; // dangerous: if the copy is
// elided then an object will be returned but you won't see the message.
}
C++ explicitly allows several optimizations involving the copy constructor that actually change the semantics of the program. (This is in contrast with most optimizations, which do not affect the semantics of the program). In particular, there are several cases where the compiler is allowed to re-use an existing object, rather than copying one, if it knows that the existing object will become unreachable. This (copy construction) is one such case; another similar case is the "return value optimization" (RVO), where if you declare the variable that holds the return value of a function, then C++ can choose to allocate that on the frame of the caller, so that it doesn't need to copy it back to the caller when the function completes.
In general, in C++, you are playing with fire if you define a copy constructor that has side effects or does anything other than just copying.
In any compiler, syntax [and semantic] analysis process are done prior to the code optimization process.
The code must be syntactically valid otherwise it won't even compile. Its only in the later phase (i.e code optimization) that the compiler decides to elide the temporary that it creates.
So you need an accessible copy c-tor.
Here you can find this (with your comment ;)):
[the standard] also says that the temporary copy
can be elided, but the semantic
constraints (eg. accessibility) of the
copy constructor still have to be
checked.
RVO and NRVO, buddy. Perfectly good case of copy ellision.
This is an optimization by the compiler.
In evaluating: A a = 10; instead of:
first constructing a temporary object through A(int);
constructing a through the copy constructor and passing in the temporary;
the compiler will simply construct a using A(int).