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.
Related
In the first time,the code looks like below:
#include "stdafx.h"
#include<iostream>
using namespace std;
class Test{
public:
explicit Test(int);
~Test();
//Test(Test&);
int varInt;
};
Test::Test(int temp){
varInt = temp;
cout << "call Test::constructor\n";
}
Test::~Test(){
cout << "call Test::destructor\n";
}
/*Test::Test(Test&temp){
varInt = temp.varInt;
cout << "call Test::copy constructor\n";
}*/
void func(Test temp){
cout << "call func\n";
}
int _tmain(int argc, _TCHAR* argv[])
{
func(Test(1));
return 0;
}
output:
call Test::constructor
call func
call Test::destructor
call Test::destructor
This confuses me,cause there's only one object that was created(as the argument of func),but two destructors were called after the function ends.
I started to wonder,is this because the default copy constructor was called?So I wrote the definition of copy constructor,which made things just more strange.
After I add the Commented-Out Code as you can see above,namely the definition of copy constructor,into the class,the output became like this:
output:
call Test::constructor
call func
call Test::destructor
Things became just right now.
Can someone explain this phenomenon to me?Thank u very much.
Your interpretation of your original code (that the implicitly-declared copy constructor is being called) is correct.
Depending on the version of the standard that your compiler is implementing, it may actually be using the implicitly-declared move constructor instead. But this amounts to the same thing.
Your modified code (where you've explicitly provided a copy constructor) happens to be triggering the copy elision optimization, where the compiler just constructs the object in the desired location to begin with. This is one of the few situations where the standard specifically allows an optimization even though it affects the observable behavior of the program (since you can tell whether your copy constructor was called).
Nothing about your modified code requires copy elision, and nothing about your original code forbids it; the two versions just happen to differ in whether they trigger the optimization in your compiler under your current settings.
Note: the situation here changes a bit in C++17, where this optimization does become mandatory in some cases. See my above link for details.
Edited to add: Incidentally, in your version with an explicit copy constructor, your constructor is unusual in taking a non-constant reference. This actually means that it can't be used anyway, since a non-constant reference can't bind to the temporary Test(1). I think this oddness may have to do with why your compiler is performing copy elision. If you change the constructor to take a constant reference, as the implicitly-declared copy constructor would, you may see the behavior you were expecting, with your explicit copy constructor being called and the destructor being called twice. (But that's just speculation on my part; you'll have to try it and see!)
You have two objects of class Test. Since you pass arguments by value, one is constructed explicitly in the main function, another one is constructed with default copy constructor, as your copy constructor is commented out. Both objects get destructed. One on the exit from func(), another at the exit from main(). Hence two destructor calls.
Recently I was testing some C++ deep and dark corners and I got confused about one subtle point. My test is so simple actually:
// problem 1
// no any constructor call, g++ acts as a function declaration to the (howmany())
// g++ turns (howmany()) into (howmany(*)())
howmany t(howmany());
// problem 2
// only one constructor call
howmany t = howmany();
My expectation from above line was; first howmany() constructor call will produce one temporary object and then compiler will use that temporary object with copy-constructor in order to instantiate t. However, output of compiler really confused me because output shows only one constructor call. My friends mentioned me about compiler pass-by-value optimization but we are not sure about it. I want to learn what does happen here ?
Output of problem 2 is below. problem 1 is completely beyond object instantiation because compiler behaves it as a function pointer declaration.
howmany()
~howmany()
My test class is:
class howmany {
public:
howmany() {
out << "howmany()" << endl;
}
howmany(int i) {
out << "howmany(i)" << endl;
}
howmany(const howmany& refhm) {
out << "howmany(howmany&)" << endl;
}
howmany& operator=(const howmany& refhm) {
out << "operator=" << endl;
}
~howmany() {
out << "~howmany()" << endl;
}
void print1() {
cout << "print1()" << endl;
}
void print2() {
cout << "print2()" << endl;
}
};
This is the most vexing parse here:
howmany t( howmany() );
In order to fix this you need to add an extra set of parens:
howmany t( (howmany()) );
^ ^
clang is very helpful here and warns you:
warning: parentheses were disambiguated as a function declaration [-Wvexing-parse]
howmany t( howmany() );
^~~~~~~~~~~~~
main.cpp:31:12: note: add a pair of parentheses to declare a variable
howmany t( howmany() );
^
( )
The other way to fix this is to use C++11 uniform initialization syntax:
howmany t{ howmany{} };
^ ^^ ^
Update
To address part 2 which you added to the question the draft standard allows for omission of the copy/move construction in some cases. We can see this from section 12.8 Copying and moving class objects paragraph 31 which says:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.122 This elision of copy/move operations, called copy elision, is permitted in the following circumstances
and includes the following bullet:
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
Yes, this is one of the optimization compiler may do.
The compiler is allowed to eliminate temporary object creation even if copy constructor has side effects!
After getting clues from above answers I found real problem. In my case, especially problem 2, compiler eliminates the copy construction process because return value of howmany() constructor call is completely same to the howmany t object, for that reason compiler just eliminates copy-construction process in order to make optimization. More detail of this problem is covered in wikipedia, please look to there.Return value optimization
-fno-elide-constructors is one of g++ flags which is responsible from disabling the optimizations. In cmake we just need to set it.
set(CMAKE_CXX_FLAGS "-fno-elide-constructors")
After disabling optimizations, the output of program turned what i was expecting.
howmany()
howmany(howmany&)
~howmany()
~howmany()
Thanks :)
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
So I have a question for you. :)
Can you tell me the output the following code should produce?
#include <iostream>
struct Optimized
{
Optimized() { std::cout << "ctor" << std::endl; }
~Optimized() { std::cout << "dtor" << std::endl; }
Optimized(const Optimized& copy) { std::cout << "copy ctor" << std::endl; }
Optimized(Optimized&& move) { std::cout << "move ctor" << std::endl; }
const Optimized& operator=(const Optimized& rhs) { std::cout << "assignment operator" << std::endl; return *this; }
Optimized& operator=(Optimized&& lhs) { std::cout << "move assignment operator" << std::endl; return *this; }
};
Optimized TestFunction()
{
Optimized a;
Optimized b = a;
return b;
}
int main(int argc, char* argv[])
{
Optimized test = TestFunction();
return 0;
}
My first response would be:
ctor
copy ctor
move ctor
dtor
dtor
dtor
and it IS true, but only if compiler optimization is turned off. When optimization is turned ON then the output is entirely different. With optimization turned on, the output is:
ctor
copy ctor
dtor
dtor
With compiler optimization, the test variable is the return variable.
My question is, what conditions would cause this to not be optimized this way?
I have always been taught that returning a struct/class which results in extra copy constructors could better be optimized by being passed in as a reference but the compiler is doing that for me. So is return a structure still considered bad form?
This is known as Copy Elision and is a special handling instead of copying/moving.
The optimization is specifically allowed by the Standard, as long as it would be possible to copy/move (ie, the method is declared and accessible).
The implementation in a compiler is generally referred to, in this case, as Return Value Optimization. There are two variations:
RVO: when you return a temporary (return "aa" + someString;)
NRVO: N for Named, when you return an object that has a name
Both are implemented by major compilers, but the latter may kick in only at higher optimization levels as it is more difficult to detect.
Therefore, to answer your question about returning structs: I would recommend it. Consider:
// Bad
Foo foo;
bar(foo);
-- foo can be modified here
// Good
Foo const foo = bar();
The latter is not only clearer, it also allows const enforcement!
Both outputs are permissible. The C++03 language standard says, in clause 12.8/15:
When certain criteria are met, an implementation is allowed to omit the copy construction of a class object,
even if the copy constructor and/or destructor for the object have side effects. In such cases, the implementation
treats the source and target of the omitted copy operation as simply two different ways of referring to
the same object, and the destruction of that object occurs at the later of the times when the two objects
would have been destroyed without the optimization.111) This elision of copy operations is permitted in the
following circumstances (which may be combined to eliminate multiple copies):
in a return statement in a function with a class return type, when the expression is the name of a
non-volatile automatic object with the same cv-unqualified type as the function return type, the copy
operation can be omitted by constructing the automatic object directly into the function’s return value
when a temporary class object that has not been bound to a reference (12.2) would be copied to a class
object with the same cv-unqualified type, the copy operation can be omitted by constructing the temporary
object directly into the target of the omitted copy
The output this code will produce is unpredictable, since the language specification explicitly allows optional elimination (elision) of "unnecessary" temporary copies of class objects even if their copy constructors have side effects.
Whether this will happen or not might depend on may factors, including the compiler optimization settings.
In my opinion calling the above copy elision an "optimization" is not entirely correct (although the desire to use this term here is perfectly understandable and it is widely used for this purpose). I'd say that the term optimization should be reserved to situations when the compiler deviates from the behavior of the abstract C++ machine while preserving the observable behavior of the program. In other words, true optimization implies violation of the abstract requirements of the language specification. Since in this case there's no violation (the copy elision is explicitly allowed by the standard), there's no real "optimization". What we observe here is just how the C++ language works at its abstract level. No need to involve the concept of "optimization" at all.
Even when passing back by value the compiler can optimise the extra copy away using Return Value Optimisation see; http://en.wikipedia.org/wiki/Return_value_optimization
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).