I have the following two functions:
Class foo(Class arg)
{
return arg;
}
Class bar(Class *arg)
{
return *arg;
}
Now, when I solely call foo(arg), the copy constructor is of course called twice. When I call bar(&arg) solely, it's only called once. Thus, I would expect
foo(bar(&arg));
the copy constructor being called three times here. However, it's still only called twice. Why is that? Does the compiler recognise that another copy is unneeded?
Thanks in advance!
Does the compiler recognise that another copy is unneeded?
Indeed it does. The compiler is performing copy/move elision. That is the only exception to the so called "as-if" rule, and it allows the compiler (under some circumstances, like the one in your example) to elide calls to the copy or move constructor of a class even if those have side effects.
Per paragraph 12.8/31 of the C++11 Standard:
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.
This elision of copy/move operations, called copy elision, 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 (other than a function or catch-clause parameter) with the same cv-unqualified
type as the function return type, the copy/move 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/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
— [...]
With GCC you can try using the -fno-elide-constructor compilation flag to suppress this optimization and see how the compiler would behave when no copy elision occurs.
Related
From the standard 6.7.7 (temporary objects), we can see:
When an object of class type X is passed to or returned from a function, if X has at least one eligible copy or move constructor ([special]), each such constructor is trivial, and the destructor of X is either trivial or deleted, implementations are permitted to create a temporary object to hold the function parameter or result object. The temporary object is constructed from the function argument or return value, respectively, and the function's parameter or return object is initialized as if by using the eligible trivial constructor to copy the temporary (even if that constructor is inaccessible or would not be selected by overload resolution to perform a copy or move of the object).
[Note 4: This latitude is granted to allow objects of class type to be passed to or returned from functions in registers. — end note]
Does it mean that copy elision is not mandatory with trivially copyable types? What I understand here is that if we declare a destructor like ~Object() {} instead of not declaring anything (so the destructor would be generated by the compiler) or a default one, the object becomes not trivially constructible, and so, the copy elision must be performed (at the condition we respect the well-known condition for copy elision to occur).
Does it mean that copy elision is not mandatory with trivially copyable types?
In principle, yes, but the goal of the section you quoted was not to exempt POD types from copy elision but to bypass ABI restrictions on how objects are passed in function calls. It allows POD objects to be passed via registers. This is the best the standard can do given the C++ abstract machine knows nothing about the physical machine and its registers and calling conventions.
Guaranteed copy elision is a result of several changes spread throughout the standard, which includes deferred prvalue materialization. For details refer to the original proposal p0135r1.
With those changes it becomes possible (and required) to initialize an object without involving temporaries ([dcl.init.general]/15.6.1):
— Otherwise, if the destination type is a (possibly
cv-qualified) class type:
If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the
class of the destination, the initializer expression is used to
initialize the destination object.
This question already has answers here:
Is there a difference between copy initialization and direct initialization?
(9 answers)
Closed 7 years ago.
Seen this in a book I'm reading:
Rectangle r(Point(200,200));
Is this the same as:
Rectangle r = Rectangle(Point(200,200));
In:
Rectangle r(Point(200,200));
you are initializing a Rectangle object via a constructor that takes a Point object.
In:
Rectangle r = Rectangle(Point(200,200));
you are constructing a Rectangle temporary object as above and then passing it to the copy/move constructor of Rectangle.
If the copy constructor is properly written, then the resulting objects are the same, but one more copy/move constructor would be theoretically called in the latter.
This is not true if the compiler decides to elide the copy, according to §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 constructor selected for the copy/move operation and/or the destructor for the object have side eects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two dierent 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 (which may be combined to eliminate multiple copies):
[...]
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
[...]
If the contructor that takes a Point is not marked explicit, then you could also have the form:
Rectangle r = Point(200,200);
I am doing it like this:
class Something;
Something f();
...
std::shared_ptr<Something> ptr(new Something(f()));
but this doesn't feel right. Moreover it needs the copy constructor. Is there a better way?
Use std::make_shared to avoid explicitly calling new. Similarly, use std::make_unique.
make_shared might be more efficient because it can allocate the counters for the smart-pointer and the object in one block together.
Still, it does not come into its own until you have at least one more way for your statement to cause an exception after construction of the object but before it is safely ensconced in its smart-pointer. Said exceptions would otherwise cause a memory-leak.
Example for bad behaviour:
void f(std::shared_ptr<int> a, std::shared_ptr<int> b);
f(std::shared_ptr<int>(new int(0)), std::shared_ptr<int>(new int(4)));
And corrected:
f(std::make_shared<int>(0), std::make_shared<int>(4));
Now, someone advises you to return Something not by value but as a dynamically allocated pointer. For your use-case, there's actually no difference with an acceptable compiler as long as Something is copyable, due to copy-ellision, aka directly constructing the returned value in the space allocated by new/make_shared/make_unique.
So, just do what you think best there.
Copy-ellision is explicitly allowed by the standard. Just be aware the copy-constructor must be accessible anyway:
12.8. Copying and moving class objects §32
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. 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.123 This elision of copy/move
operations, called copy elision, 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 (other than a function or catch-clause parameter) with the same cvunqualified
type as the function return type, the copy/move operation can be omitted by constructing
the automatic object directly into the function’s return value
— in a throw-expression, when the operand is the name of a non-volatile automatic object (other than
a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost
enclosing try-block (if there is one), the copy/move operation from the operand to the exception
object (15.1) can be omitted by constructing the automatic object directly into the exception object
— 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
— when the exception-declaration of an exception handler (Clause 15) declares an object of the same type
(except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted
by treating the exception-declaration as an alias for the exception object if the meaning of the program
will be unchanged except for the execution of constructors and destructors for the object declared by
the exception-declaration.
You can use std::make_shared.
It is better to use it for the following reason:
This function typically allocates memory for the T object and for the shared_ptr's control block with a single memory allocation (it is a non-binding requirement in the Standard). In contrast, the declaration std::shared_ptr p(new T(Args...)) performs at least two memory allocations, which may incur unnecessary overhead.
The better way would be to have f() return Something* (allocated with new) or shared_ptr<Something>. Otherwise, the Something returned by f() will have automatic storage and putting it in a shared_ptr doesn't make sense. You could, in theory, use a shared_ptr with a custom deleter, but that wouldn't change the storage class of the underlying object, and you'd most likely just end up with a wild pointer.
If you can't change f(), your solution of making a copy with dynamic storage is really all you can do. If you can give Something a move constructor, you could at least reduce the cost of making the copy (assuming it's expensive enough to be worth reducing).
But see this answer for why the copy isn't worth worrying about. Do whatever you think makes the code most readable.
Recently, I've "played" with rvalues to understand their behavior. Most result didn't surprize me, but then I saw that if I throw a local variable, the move constructor is invoked.
Until then, I thought that the purpose of move semantics rules is to guarantee that object will move (and become invalid) only if the compiler can detect that it will not be used any more (as in temporary objects), or the user promise not to use it (as in std::move).
However, in the following code, none of this condition held, and my variable is still being moved (at least on g++ 4.7.3).
Why is that?
#include <iostream>
#include <string>
using namespace std;
int main() {
string s="blabla";
try {
throw s;
}
catch(...) {
cout<<"Exception!\n";
}
cout<<s; //prints nothing
}
C++ standard says (15.1.3):
Throwing an exception copy-initializes (8.5, 12.8) a temporary object, called the exception object. The temporary is an lvalue and is used to initialize the variable named in the matching handler (15.3).
This paragraph may be also relevant here (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 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. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
(...)
— in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object
Checked in Visual Studio 2012, effect:
Exception!
blabla
It looks like a bug in GCC indeed.
In the given case, it is probably a compiler bug, because the variable thrown (and moved from) is referenced afterwards.
In general case invoking move on throw is conceptually same as moving on return. It is good to invoke move automatically when it is known that the variable could not be referenced after the given point (throw or return).
Consider an exception class with a copy constructor with side-effects.
Can a compiler skip calling the copy constructor here:
try {
throw ugly_exception();
}
catch(ugly_exception) // ignoring the exception, so I'm not naming it
{ }
What about this:
try {
something_that_throws_ugly_exception();
}
catch(ugly_exception) // ignoring the exception, so I'm not naming it
{ }
(yes, I know this is all very ugly, this was inspired by another question)
Yes, it can be elided both during throwing and catching. For catching it can be elided only when the type specified in the catch clause is the same (save for cv-qualifications) as the type of the exception object. For more formal and detailed description see C++11 12.8/31.
...This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):
...
in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object
...
when the exception-declaration of an exception handler (Clause 15) declares an object of the same type (except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.
I think this is specifically permitted. For C++03, 15.1/3 says:
A throw-expression initializes a temporary object, called the
exception object,
and 12/15 says:
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 tempo-
rary object directly into the target of the omitted copy
So, the secret hiding place where in-flight exceptions are kept, is defined by the standard to be a temporary, and hence is valid for copy-elision.
Edit: oops, I've now read further. 15.1/5:
If the use of the temporary object can be eliminated without changing
the meaning of the program except for the execution of constructors
and destructors associated with the use of the temporary object
(12.2), then the exception in the handler can be initialized directly
with the argument of the throw expression.
Doesn't get much clearer.
Whether it actually will... if the catch clause were to re-raise the exception (including if it called non-visible code that might do so), then the implementation needs that "temporary object called the exception object" still to be around. So there might be some restrictions on when that copy elision is possible. Clearly an empty catch clause can't re-raise it, though.
Yes. If the catch catches the exception by reference, then there will not be copy (well, that is by definition).
But I think that is not your question, and I believe the code which you've written is written on purpose with no mention of reference. If that is the case, then yes, even in this case, copy can be elided. Actually initialization of the variable in the catch is direct-initialization in theory. And copy in a direct-initialization can be elided by the compiler where it's possible.
C++03 §8.5/14 reads,
[...] In certain cases, an implementation is permitted to eliminate the copying inherent in this direct-initialization by constructing the intermediate result directly into the object being initialized;