C++17 copy elision and object destruction - c++

From cppreference,
When copy elision occurs, the implementation treats the source and
target of the omitted copy/move (since C++11) 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 (except that, if
the parameter of the selected constructor is an rvalue reference to
object type, the destruction occurs when the target would have been
destroyed) (since C++17).
For a simple case like A a = returnA();, I can understand that the object is not destroyed in returnA() and instead the destruction occurs as in the case A a; which is the later time.
I can't think of a case which the opposite happens such that the source of the copy/move operation is destroyed first. Also I would like an example of the added statement since C++17 (exception when parameter of selected constructor is an rvalue reference to object type)

The symmetric case where the source outlives the target is when the prvalue is a parameter:
struct A {
static int *data;
A() {if(!refs++) data=new int(42);}
A(const A&) {++refs;} // not movable
~A() {if(!--refs) delete data;}
private:
static int refs;
};
int A::refs,*A::data;
int* f(A) {return A::data;}
A returnA();
int returnInt() {return *f(returnA());} // ok
Because the result of returnA() is a temporary, its lifetime extends to the end of the return statement’s full-expression. The implementation may identify it with f’s parameter, but may not destroy it when f returns, so the dereference in returnInt is valid. (Note that parameters may survive that long anyway.)
The adjustment in C++17 (along with such elision being guaranteed) is that if you (would) move the prvalue, it may be destroyed when the parameter is (since you shouldn’t be relying on its contents anyway). If that’s when f returns, the (ill-advised) code above becomes invalid if A is made movable.

Related

Temporary object argument lifetime in a function

I've read several posts about temporary object's lifetime. And in a word I learn that:
the temporary is destroyed after the end of the full-expression
containing
it.
But this code is out of my expectation:
#include <memory>
#include <iostream>
void fun(std::shared_ptr<int> sp)
{
std::cout << "fun: sp.use_count() == " << sp.use_count() << '\n';
//I expect to get 2 not 1
}
int main()
{
fun(std::make_shared<int>(5));
}
So I think I have 2 smart pointer objects here, one is std::make_shared<int>(5), the temporary unnamed object and the other sp which is a local variable inside the function. So based on my understanding, the temporary one won't "die" before completing the function call. I expect output to be 2 not 1. What's wrong here?
Pre-C++17, sp is move-constructed from the temporary if the move is not elided to begin with. In either case, sp is the sole owner of the resource, so the use count is rightly reported as 1. This is overload 10)† in this reference.
While the temporary still exists, if not elided, it is in a moved-from state and no longer holds any resource, so it doesn't contribute to the resource's use count.
Since C++17, no temporary is created thanks to guaranteed copy/move elision, and sp is constructed in place.
† Exact wording from said reference:
10) Move-constructs a shared_ptr from r. After the construction, *this contains a copy of the previous state of r, r is empty and its stored pointer is null. [...]
In our case, r refers to the temporary and *this to sp.
c++ has a strange concept known as elision.
Elision is a process whereby the compiler is allowed to take the lifetime of two objects and merge them. Typically people say that the copy or move constructor "is elided", but what is really elided is the identity of two seemingly distinct objects.
As a rule of thumb, when an anonymous temporary object is used to directly construct another object, their lifetimes can be elided together. So:
A a = A{}; // A{} is elided with a
void f(A);
f(A{}); // temporary A{} is elided with argument of f
A g();
f(g()); // return value of g is elided with argument of f
There are also situations where named variables can be elided with return values, and more than two objects can be elided together:
A g() {
A a;
return a; // a is elided with return value of g
}
A f() {
A x = g(); // x is elided with return value of g
// which is elided with a within g
return x; // and is then elided with return value of f
}
A bob = f(); // and then elided with bob.
Only one instance of A exists in the above code; it just has many names.
In c++17 things go even further. Prior to that the objects in question had to logically be copyable/movable, and elision simply eliminated calls the the constructor and shared the objects identity.
After c++17 some things that used to be elision are (in some sense) "guaranteed elision", which is really a different thing. "Guaranteed elision" is basically the idea that prvalues (things that used to be temporaries in pre-c++17) are now abstract instructions on how to create an object.
In certain circumstances temporaries are instantiated from them, but in others they are just used to construct some other object in some other spot.
So in c++17 you should think of this function:
A f();
as a function that returns instructions on how to create a A. When you do this:
A a = f();
you are saying "use the instructions that f returns to construct an A named a".
Similarly, A{} is no longer a temporary but instructions no how to create an A. If you put it on a line by itself those instructions are used to create a temporary, but in most contexts no temporary logically or actually exists.
template<class T, class...Us>
std::shared_ptr<T> make_shared(Us&&...);
this is a function that returns instructions on how to create a shared_ptr<T>.
fun(std::make_shared<int>(5));
here you apply these instructions to the agument of fun, which is of type std::shared_ptr<int>.
In pre-[C++17] without hostile compiler flags, the result with elision is practically the same here. In that case, the temporaries identity is merged with the argument of fun.
In no practical case will there be a temporary shared_ptr with a reference count of 0; other answers which claim this are wrong. The one way where that can occur is if you pass in flags that your compiler from performing elision (the above hostile compiler flags).
If you do pass in such flags, the shared_ptr is moved-from into the argument of fun, and it exists with a reference count of 0. So use_count will remain 0.
In addition to the move construction of std::shared_ptr, there is another aspect to consider: in-place creation of function argument passed by value. This is an optimization that compilers usually do. Consider the exemplary type
struct A {
A() { std::cout << "ctor\n"; }
A(const A&) { std::cout << "copy ctor\n"; }
};
together with a function that takes an instance of A by value
void f(A) {}
When the function parameter is passed as an rvalue like this
f(A{});
the copy constructor won't be called unless you explicitly compile with -fno-elide-constructors. In C++17, you can even delete the copy constructor
A(const A&) = delete;
because the copy elision is guaranteed. With this in mind: the temporary object that you pass as a function argument is "destroyed after the end of the full-expression containing it" only if there is a temporary, and a code snippet might suggest the existence of one even though it's easily (and since C++17: guaranteed to be) optimized out.

Why the expression 'T foo = T()' is not copy initialized [duplicate]

Here is the little code snippet:
class A
{
public:
A(int value) : value_(value)
{
cout <<"Regular constructor" <<endl;
}
A(const A& other) : value_(other.value_)
{
cout <<"Copy constructor" <<endl;
}
private:
int value_;
};
int main()
{
A a = A(5);
}
I assumed that output would be "Regular Constructor" (for RHS) followed by "Copy constructor" for LHS. So I avoided this style and always declared variable of class as A a(5);. But to my surprise in the code above copy constructor is never called (Visual C++ 2008)
Does anybody know if this behavior is a result of compiler optimization, or some documented (and portable) feature of C++? Thanks.
From another comment: "So by default I should not rely on it (as it may depend on the compiler)"
No, it does not depend on the compiler, practically anyway. Any compiler worth a grain of sand won't waste time constructing an A, then copying it over.
In the standard it explicitly says that it is completely acceptable for T = x; to be equivalent to saying T(x);. (§12.8.15, pg. 211) Doing this with T(T(x)) is obviously redundant, so it removes the inner T.
To get the desired behavior, you'd force the compiler to default construct the first A:
A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);
I was researching this to answer another question that was closed as a dupe, so in order to not let the work go to waste I 'm answering this one instead.
A statement of the form A a = A(5) is called copy-initialization of the variable a. The C++11 standard, 8.5/16 states:
The function selected is called with the initializer expression as
its argument; if the function is a constructor, the call initializes a
temporary of the cv-unqualified version of the destination type. The
temporary is a prvalue. The result of the call (which is the temporary
for the constructor case) is then used to direct-initialize, according
to the rules above, the object that is the destination of the
copy-initialization. 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; see 12.2, 12.8.
This means that the compiler looks up the appropriate constructor to handle A(5), creates a temporary and copies that temporary into a. But under what circumstances can the copy be eliminated?
Let's see what 12.8/31 says:
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. 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
Having all this in mind, here's what happens with the expression A a = A(5):
The compiler sees a declaration with copy-initialization
The A(int) constructor is selected to initialize a temporary object
Because the temporary object is not bound to a reference, and it does have the same type A as the destination type in the copy-initialization expression, the compiler is permitted to directly construct an object into a, eliding the temporary
Here you have copy-initialization of a from temporary A(5). Implementation allowed to skip calling copy constructor here according to C++ Standard 12.2/2.
A a = A(5);
This line is equivalent to
A a(5);
Despite its function-style appearance, the first line simply constructs a with the argument 5. No copying or temporaries are involved. From the C++ standard, section 12.1.11:
A functional notation type conversion (5.2.3) can be used to create new objects of its type. [ Note: The
syntax looks like an explicit call of the constructor. —end note ]

Can't return unique_ptr element from an array by value [duplicate]

unique_ptr<T> does not allow copy construction, instead it supports move semantics. Yet, I can return a unique_ptr<T> from a function and assign the returned value to a variable.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
The code above compiles and works as intended. So how is it that line 1 doesn't invoke the copy constructor and result in compiler errors? If I had to use line 2 instead it'd make sense (using line 2 works as well, but we're not required to do so).
I know C++0x allows this exception to unique_ptr since the return value is a temporary object that will be destroyed as soon as the function exits, thus guaranteeing the uniqueness of the returned pointer. I'm curious about how this is implemented, is it special cased in the compiler or is there some other clause in the language specification that this exploits?
is there some other clause in the language specification that this exploits?
Yes, see 12.8 §34 and §35:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object [...]
This elision of copy/move operations, called copy elision, is permitted [...]
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 [...]
When the criteria for elision of a copy operation are met and the object to be copied is designated by an lvalue,
overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
Just wanted to add one more point that returning by value should be the default choice here because a named value in the return statement in the worst case, i.e. without elisions in C++11, C++14 and C++17 is treated as an rvalue. So for example the following function compiles with the -fno-elide-constructors flag
std::unique_ptr<int> get_unique() {
auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
return ptr; // <- 2, moved into the to be returned unique_ptr
}
...
auto int_uptr = get_unique(); // <- 3
With the flag set on compilation there are two moves (1 and 2) happening in this function and then one move later on (3).
This is in no way specific to std::unique_ptr, but applies to any class that is movable. It's guaranteed by the language rules since you are returning by value. The compiler tries to elide copies, invokes a move constructor if it can't remove copies, calls a copy constructor if it can't move, and fails to compile if it can't copy.
If you had a function that accepts std::unique_ptr as an argument you wouldn't be able to pass p to it. You would have to explicitly invoke move constructor, but in this case you shouldn't use variable p after the call to bar().
void bar(std::unique_ptr<int> p)
{
// ...
}
int main()
{
unique_ptr<int> p = foo();
bar(p); // error, can't implicitly invoke move constructor on lvalue
bar(std::move(p)); // OK but don't use p afterwards
return 0;
}
unique_ptr doesn't have the traditional copy constructor. Instead it has a "move constructor" that uses rvalue references:
unique_ptr::unique_ptr(unique_ptr && src);
An rvalue reference (the double ampersand) will only bind to an rvalue. That's why you get an error when you try to pass an lvalue unique_ptr to a function. On the other hand, a value that is returned from a function is treated as an rvalue, so the move constructor is called automatically.
By the way, this will work correctly:
bar(unique_ptr<int>(new int(44));
The temporary unique_ptr here is an rvalue.
I think it's perfectly explained in item 25 of Scott Meyers' Effective Modern C++. Here's an excerpt:
The part of the Standard blessing the RVO goes on to say that if the conditions for the RVO are met, but compilers choose not to perform copy elision, the object being returned must be treated as an rvalue. In effect, the Standard requires that when the RVO is permitted, either copy elision takes place or std::move is implicitly applied to local objects being returned.
Here, RVO refers to return value optimization, and if the conditions for the RVO are met means returning the local object declared inside the function that you would expect to do the RVO, which is also nicely explained in item 25 of his book by referring to the standard (here the local object includes the temporary objects created by the return statement). The biggest take away from the excerpt is either copy elision takes place or std::move is implicitly applied to local objects being returned. Scott mentions in item 25 that std::move is implicitly applied when the compiler choose not to elide the copy and the programmer should not explicitly do so.
In your case, the code is clearly a candidate for RVO as it returns the local object p and the type of p is the same as the return type, which results in copy elision. And if the compiler chooses not to elide the copy, for whatever reason, std::move would've kicked in to line 1.
One thing that i didn't see in other answers is To clarify another answers that there is a difference between returning std::unique_ptr that has been created within a function, and one that has been given to that function.
The example could be like this:
class Test
{int i;};
std::unique_ptr<Test> foo1()
{
std::unique_ptr<Test> res(new Test);
return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
// return t; // this will produce an error!
return std::move(t);
}
//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));
I would like to mention one case where you must use std::move() otherwise it will give an error.
Case: If the return type of the function differs from the type of the local variable.
class Base { ... };
class Derived : public Base { ... };
...
std::unique_ptr<Base> Foo() {
std::unique_ptr<Derived> derived(new Derived());
return std::move(derived); //std::move() must
}
Reference: https://www.chromium.org/developers/smart-pointer-guidelines
I know it's an old question, but I think an important and clear reference is missing here.
From https://en.cppreference.com/w/cpp/language/copy_elision :
(Since C++11) In a return statement or a throw-expression, if the compiler cannot perform copy elision but the conditions for copy elision are met or would be met, except that the source is a function parameter, the compiler will attempt to use the move constructor even if the object is designated by an lvalue; see return statement for details.

Returning unique_ptr from functions

unique_ptr<T> does not allow copy construction, instead it supports move semantics. Yet, I can return a unique_ptr<T> from a function and assign the returned value to a variable.
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> foo()
{
unique_ptr<int> p( new int(10) );
return p; // 1
//return move( p ); // 2
}
int main()
{
unique_ptr<int> p = foo();
cout << *p << endl;
return 0;
}
The code above compiles and works as intended. So how is it that line 1 doesn't invoke the copy constructor and result in compiler errors? If I had to use line 2 instead it'd make sense (using line 2 works as well, but we're not required to do so).
I know C++0x allows this exception to unique_ptr since the return value is a temporary object that will be destroyed as soon as the function exits, thus guaranteeing the uniqueness of the returned pointer. I'm curious about how this is implemented, is it special cased in the compiler or is there some other clause in the language specification that this exploits?
is there some other clause in the language specification that this exploits?
Yes, see 12.8 §34 and §35:
When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object [...]
This elision of copy/move operations, called copy elision, is permitted [...]
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 [...]
When the criteria for elision of a copy operation are met and the object to be copied is designated by an lvalue,
overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.
Just wanted to add one more point that returning by value should be the default choice here because a named value in the return statement in the worst case, i.e. without elisions in C++11, C++14 and C++17 is treated as an rvalue. So for example the following function compiles with the -fno-elide-constructors flag
std::unique_ptr<int> get_unique() {
auto ptr = std::unique_ptr<int>{new int{2}}; // <- 1
return ptr; // <- 2, moved into the to be returned unique_ptr
}
...
auto int_uptr = get_unique(); // <- 3
With the flag set on compilation there are two moves (1 and 2) happening in this function and then one move later on (3).
This is in no way specific to std::unique_ptr, but applies to any class that is movable. It's guaranteed by the language rules since you are returning by value. The compiler tries to elide copies, invokes a move constructor if it can't remove copies, calls a copy constructor if it can't move, and fails to compile if it can't copy.
If you had a function that accepts std::unique_ptr as an argument you wouldn't be able to pass p to it. You would have to explicitly invoke move constructor, but in this case you shouldn't use variable p after the call to bar().
void bar(std::unique_ptr<int> p)
{
// ...
}
int main()
{
unique_ptr<int> p = foo();
bar(p); // error, can't implicitly invoke move constructor on lvalue
bar(std::move(p)); // OK but don't use p afterwards
return 0;
}
unique_ptr doesn't have the traditional copy constructor. Instead it has a "move constructor" that uses rvalue references:
unique_ptr::unique_ptr(unique_ptr && src);
An rvalue reference (the double ampersand) will only bind to an rvalue. That's why you get an error when you try to pass an lvalue unique_ptr to a function. On the other hand, a value that is returned from a function is treated as an rvalue, so the move constructor is called automatically.
By the way, this will work correctly:
bar(unique_ptr<int>(new int(44));
The temporary unique_ptr here is an rvalue.
I think it's perfectly explained in item 25 of Scott Meyers' Effective Modern C++. Here's an excerpt:
The part of the Standard blessing the RVO goes on to say that if the conditions for the RVO are met, but compilers choose not to perform copy elision, the object being returned must be treated as an rvalue. In effect, the Standard requires that when the RVO is permitted, either copy elision takes place or std::move is implicitly applied to local objects being returned.
Here, RVO refers to return value optimization, and if the conditions for the RVO are met means returning the local object declared inside the function that you would expect to do the RVO, which is also nicely explained in item 25 of his book by referring to the standard (here the local object includes the temporary objects created by the return statement). The biggest take away from the excerpt is either copy elision takes place or std::move is implicitly applied to local objects being returned. Scott mentions in item 25 that std::move is implicitly applied when the compiler choose not to elide the copy and the programmer should not explicitly do so.
In your case, the code is clearly a candidate for RVO as it returns the local object p and the type of p is the same as the return type, which results in copy elision. And if the compiler chooses not to elide the copy, for whatever reason, std::move would've kicked in to line 1.
One thing that i didn't see in other answers is To clarify another answers that there is a difference between returning std::unique_ptr that has been created within a function, and one that has been given to that function.
The example could be like this:
class Test
{int i;};
std::unique_ptr<Test> foo1()
{
std::unique_ptr<Test> res(new Test);
return res;
}
std::unique_ptr<Test> foo2(std::unique_ptr<Test>&& t)
{
// return t; // this will produce an error!
return std::move(t);
}
//...
auto test1=foo1();
auto test2=foo2(std::unique_ptr<Test>(new Test));
I would like to mention one case where you must use std::move() otherwise it will give an error.
Case: If the return type of the function differs from the type of the local variable.
class Base { ... };
class Derived : public Base { ... };
...
std::unique_ptr<Base> Foo() {
std::unique_ptr<Derived> derived(new Derived());
return std::move(derived); //std::move() must
}
Reference: https://www.chromium.org/developers/smart-pointer-guidelines
I know it's an old question, but I think an important and clear reference is missing here.
From https://en.cppreference.com/w/cpp/language/copy_elision :
(Since C++11) In a return statement or a throw-expression, if the compiler cannot perform copy elision but the conditions for copy elision are met or would be met, except that the source is a function parameter, the compiler will attempt to use the move constructor even if the object is designated by an lvalue; see return statement for details.

Why copy constructor is not called in this case?

Here is the little code snippet:
class A
{
public:
A(int value) : value_(value)
{
cout <<"Regular constructor" <<endl;
}
A(const A& other) : value_(other.value_)
{
cout <<"Copy constructor" <<endl;
}
private:
int value_;
};
int main()
{
A a = A(5);
}
I assumed that output would be "Regular Constructor" (for RHS) followed by "Copy constructor" for LHS. So I avoided this style and always declared variable of class as A a(5);. But to my surprise in the code above copy constructor is never called (Visual C++ 2008)
Does anybody know if this behavior is a result of compiler optimization, or some documented (and portable) feature of C++? Thanks.
From another comment: "So by default I should not rely on it (as it may depend on the compiler)"
No, it does not depend on the compiler, practically anyway. Any compiler worth a grain of sand won't waste time constructing an A, then copying it over.
In the standard it explicitly says that it is completely acceptable for T = x; to be equivalent to saying T(x);. (§12.8.15, pg. 211) Doing this with T(T(x)) is obviously redundant, so it removes the inner T.
To get the desired behavior, you'd force the compiler to default construct the first A:
A a;
// A is now a fully constructed object,
// so it can't call constructors again:
a = A(5);
I was researching this to answer another question that was closed as a dupe, so in order to not let the work go to waste I 'm answering this one instead.
A statement of the form A a = A(5) is called copy-initialization of the variable a. The C++11 standard, 8.5/16 states:
The function selected is called with the initializer expression as
its argument; if the function is a constructor, the call initializes a
temporary of the cv-unqualified version of the destination type. The
temporary is a prvalue. The result of the call (which is the temporary
for the constructor case) is then used to direct-initialize, according
to the rules above, the object that is the destination of the
copy-initialization. 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; see 12.2, 12.8.
This means that the compiler looks up the appropriate constructor to handle A(5), creates a temporary and copies that temporary into a. But under what circumstances can the copy be eliminated?
Let's see what 12.8/31 says:
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. 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
Having all this in mind, here's what happens with the expression A a = A(5):
The compiler sees a declaration with copy-initialization
The A(int) constructor is selected to initialize a temporary object
Because the temporary object is not bound to a reference, and it does have the same type A as the destination type in the copy-initialization expression, the compiler is permitted to directly construct an object into a, eliding the temporary
Here you have copy-initialization of a from temporary A(5). Implementation allowed to skip calling copy constructor here according to C++ Standard 12.2/2.
A a = A(5);
This line is equivalent to
A a(5);
Despite its function-style appearance, the first line simply constructs a with the argument 5. No copying or temporaries are involved. From the C++ standard, section 12.1.11:
A functional notation type conversion (5.2.3) can be used to create new objects of its type. [ Note: The
syntax looks like an explicit call of the constructor. —end note ]