This question already has answers here:
C++ unexpected behaviour (where are my temporaries!?)
(4 answers)
Closed 8 years ago.
Why isn't destructor for temporary object called after evaluating a full-expression:
#include <iostream>
struct A
{
int a;
A();
~A();
};
A::~A()
{
std::cout << "~A()" << std::endl;
}
A::A()
{
std::cout << "A()" << std::endl;
}
int main()
{
A b = A(); //Constructing of temporary object and applies copy-initalization
std::cout << "side effect" << std::endl;
//Destructor calling.
}
DEMO
Output:
A()
side effect
~A()
But 12.2/3 [class.temporary] says:
When an implementation introduces a temporary object of a class that
has a non-trivial constructor (12.1, 12.8), it shall ensure that a
constructor is called for the temporary object. Similarly, the
destructor shall be called for a temporary with a non-trivial
destructor (12.4). Temporary objects are destroyed as the last step in
evaluating the full-expression (1.9) that (lexically) contains the
point where they were created.
With your compiler and options the temporary is elided (optimized away), which is permitted.
Thus there is no temporary.
Thus there is no missing constructor and destructor call pair.
It's also well worth noting that the copy and move constructors are the only constructors where the compiler is allowed to assume that the constructor has no side effects, even when it knows better.
C++11 §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. […]
Related
I have the following:
#include <iostream>
#include <utility>
class T {
public:
T() {std::cout << "Default constructor called" << std::endl;}
T(const T&) {std::cout << "Copy constructor called" << std::endl;}
};
static T s_t;
T foo() {
return s_t;
}
class A {
public:
A() = delete;
A(T t) : _t(t) {}
private:
T _t;
};
int main() {
std::cout << "Starting main" << std::endl;
A a(foo());
return 0;
}
When I compile it with the line:
g++ -std=c++17 -O3 test.cpp and run it, I get the following output
Default constructor called
Starting main
Copy constructor called
Copy constructor called
My question is: since the constructor of A is taking an r-value object of type T that is only used to initialize _t, is it possible for the compiler to avoid the second copy operation and just copy s_t directly into _t?
(I understand that I can potentially replace the second copy with a move by implementing a move constructor for T)
Your copy constructor has an observable side effect (output). The (almost) golden rule of C++ optimizations is the as-if rule: Observable side effects must not be changed. But one of the two exceptions from this rule is the elision of some copy operations, specified here:
http://eel.is/c++draft/class.copy.elision
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.
But this is only allowed in very specific circumstances:
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 [...]
Your variable does not have automatic storage duration (i.e. it is not a local variable).
in a throw-expression, [...]
We are not a throw-expression.
in a coroutine [...]
We are not in a coroutine.
when the exception-declaration of an exception handler ([except]) declares an object of the same type (except for cv-qualification) as the exception object ([except.throw]) [...]
This is not the case either.
Neither the return of a global variable from a function, nor the use of a constructor parameter to initialize a member variable is covered here, so it would be illegal for any compiler to elide these copies in the code you show.
That said, if the compiler can prove that there are no side effects (which, in the case of most compilers, include system calls and memory allocations), it is of course free to elide the copy under the as-if rule, just like for all other optimizations.
I'm observing a strange behavior I don't quite understand with respect to destructors and (default) copy and move assignments.
Let's say I have a class B that has default everything and a class Test that has custom destructor, default copy assignment, and (potentially) default move assignment.
Then we create an instance of B, assign it to a variable, and replace with a new instance using assignment (where the right side is rvalue).
Two things seems weird to me and I can't see the reason for them in documentation.
When Test doesn't have move assignment (thus its copy assignment is called) the destructor of T1 object isn't explicitely called. I assume that in this case the idiomatic thing to do is to clean the resources as part of the copy assignment. Why is it different when move assignment is there (and called), however? If its there the Test's destructor is called explicitely (?by the operator).
The documentation specifies that the other after move assignment can be left in whatever state. How come the destructor for T2's temporal rvalue (i.e. the right side of =B("T2")) isn't called in case B's member doesn't have move assignment?
Playground code: https://onlinegdb.com/S1lCYmkKOV
#include <iostream>
#include <string>
class Test
{
public:
std::string _name;
Test(std::string name) : _name(name) { }
~Test()
{
std::cout << "Destructor " << _name << std::endl;
}
Test& operator=(const Test& fellow) = default;
//Test & operator= ( Test && ) = default;
};
class B {
public:
Test t;
B() : t("T0") {}
B(std::string n) : t(n) {}
};
int fce(B& b)
{
std::cout << "b = B(T2)\n";
b = B("T2");
std::cout << "return 0\n";
return 0;
}
int main() {
B b("T1");
std::cout << "fce call\n";
fce(b);
std::cout << "fce end " << b.t._name << std::endl;
}
Output with move:
fce call
b = B(T2)
Destructor T1
return 0
fce end T2
Destructor T2
Output without move:
fce call
b = B(T2)
Destructor T2
return 0
fce end T2
Destructor T2
Default move assignment calls destructor, copy assignment doesn't
Both assignments result in the destruction of a temporary B object, so the destructor is called.
replace with a new instance using assignment
Pedantic note: Assignment doesn't replace instances. The instance remains the same; the value of the instance is modified. This distinction may be subtle, but may also be relevant to your confusion.
When Test doesn't have move assignment (thus its copy assignment is called) the destructor of T1 object isn't explicitely called.
It's somewhat unclear what you mean by "T1 object". The variable b that you initialised with "T1" is destroyed. But when it is destroyed, it's value has previously been assigned to "T2", so that's what the destructor inserts into cout. This happens in both move and copy cases, and this is the second Destructor TX line in the output.
Why is it different when move assignment is there (and called), however?
The difference is when the temporary object from the line b = B("T2") is destroyed. This is the first Destructor TX line in the output.
After copy assignment, this temporary will still hold the "T2" value, so that's what you see in the destructor.
After move assignment, the temporary is no longer guaranteed to contain "T2", but rather it is left in a valid but unspecified state (as described in specification of std::string), so output could be anything. In this case it happened to be "T1". (Based on this result, we might make a guess that the move assignment operator of the string may have been implemented by swapping the internal buffers. This observation is not a guaranteed behaviour).
The documentation specifies that the other after move assignment can be left in whatever state. How come the destructor for T2's temporal rvalue (i.e. the right side of =B("T2")) isn't called in case B's member doesn't have move assignment?
The destructor of the temporary is called. The temporary simply is no longer in the state described by "contains "T2"" after it has been moved from.
This question already has answers here:
What are copy elision and return value optimization?
(5 answers)
Closed 4 years ago.
Today I encountered something I don't really understand about copy constructor.
Consider the next code:
#include <iostream>
using namespace std;
class some_class {
public:
some_class() {
}
some_class(const some_class&) {
cout << "copy!" << endl;
}
some_class call() {
cout << "is called" << endl;
return *this; // <-- should call the copy constructor
}
};
some_class create() {
return some_class();
}
static some_class origin;
static some_class copy = origin; // <-- should call the copy constructor
int main(void)
{
return 0;
}
then the copy constructor is called when assigning origin to copy, which makes sense. However, if I change the declaration of copy into
static some_class copy = some_class();
it isn't called. Even when I am using the create() function, it does not call the copy constructor.
However, when changing it to
static some_class copy = some_class().call();
it does call the copy constructor.
Some research explained that the compiler is allowed to optimize the copy-constructor out, which sound like a good thing. Until the copy-constructor is non-default, as then it might, or might not do something obvious, right?
So when is it allowed for the compiler to optimize out the copy-constructor?
Since C++17, this kind of copy elision is guaranteed, the compiler is not just allowed, but required to omit the copy (or move) construction, even if the copy (or move) constructor has the observable side-effects.
Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:
In the initialization of a variable, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the
variable type:
T x = T(T(T())); // only one call to default constructor of T, to initialize x
In a return statement, when the operand is a prvalue of the same class type (ignoring cv-qualification) as the function return type:
T f() {
return T();
}
f(); // only one call to default constructor of T
Your code snippet matches these two cases exactly.
Before C++17, the compiler is not required, but allowed to omit the copy (or move) construction, even if the copy/move constructor has observable side-effects. Note that it's an optimization, even copy elision takes place the copy (or move) constructor still must be present and accessible.
On the other hand, call() doesn't match any conditions of copy elision; it's returning *this, which is neither a prvalue nor a object with automatic storage duration, copy construction is required and can't be omitted.
Why this:
struct A
{
A(int) {
cout << "construct from int" << endl;
}
A(A&&) = delete;
A(const A &) {
cout << "copy constructor" << endl;
}
};
int main(){
A a = 0;
}
gives me an error:
error: use of deleted function ‘A::A(A&&)’
And why when I add such move constructor
A(A&&) {
cout << "move constructor" << endl;
}
it compiles fine, but programm's output is just
construct from int
So as far as I understand, compiler asks for constructor but doesn't use it. Why? This makes no sense to me.
P.S. I assume that
A a = 0;
is equvalent of
A a = A(0);
but why neither move constructor nor move assignment operator is called?
According to the C++ Standard (12.8 Copying and moving class objects)
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.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
and
30 A program is ill-formed if the copy/move constructor or the
copy/move assignment operator for an object is implicitly odr-used and
the special member function is not accessible (Clause 11). [ Note:
Copying/moving one object into another using the copy/move constructor
or the copy/move assignment operator does not change the layout or
size of either object. —end note ]
#include<iostream>
#include<string>
using namespace std;
class B;
class A {
public:
A(const A&) { //copy ctor
cout << "Copy ctor A\n";
}
A() {} //default ctor
A(const B&) { //lets call this conversion ctor
cout << "B to A conversion ctor\n";
}
};
class B {
public:
};
int main()
{
B b;
A a = b;
}
The above code prints
B to A conversion ctor.
But as per what I have discovered after looking around for a while, it should print
B to A conversion ctor
Copy ctor A
As first a temporary object of type A is created by conversion ctor and then that object is copied into a wherein copy ctor gets called. Also, when copy ctor is made private, statement A a = b; generates this error:
‘A::A(const A&)’ is private
which is obvious as to copy the temporary object into a copy ctor must be visible.
So my question is why copy ctor is not being eventually called as evident from the output(please correct if am leading wrong somewhere here) even though it is required to be accessible?
A a = b;
This form is called copy-initialization. The applicable rule states that in this case a temporary A object will be constructed from the B instance and that temporary will then be used to direct-initialize a.
However, the compiler is allowed to elide the copy as it is not necessary. Even though the elision is allowed, the class still needs to be copyable for the form to be valid.
You can see the result without elision by passing -fno-elide-constructors to GCC or Clang.
why copy ctor is not being eventually called as evident from the output
According to the standard, an implementation is allowed to omit the copy/move construction in certain criteria. In this case, a temporary A(constructed from b, and will be copied to a) 's construction is omitted.
$12.8/31 Copying and moving class objects [class.copy]
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.
And
(31.3) — 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
type (ignoring cv-qualification), the copy/move operation can be
omitted by constructing the temporary object directly into the target
of the omitted copy/move