The question is pretty much in the title, I need help with understanding where exactly the copy constructor is called, as the output of this code doesn't match what I learned in class.
Are you sure that is not 31,23,25?
When I changed return x2, to return 2; I didn't get a third copy constructor called.
And this rise my question in class teacher said that the copy constructor would be called in both line 21 and line 25, did he was just wrong?
#include <iostream>
#include <string>
struct X
{
explicit X( int id = 0 ) noexcept : id(id)
{ std::cout << "X::constructor: id == " << id << " object at " << this << '\n' ; }
X( const X& that ) noexcept : id(that.id)
{
std::cout << "X::copy_constructor: id == " << id << " object at " << this
<< ", copied from object at " << std::addressof(that) << '\n' ;
}
X( X&& that ) noexcept : id(that.id)
{
std::cout << "X::move_constructor: id == " << id << " object at " << this
<< ", moved from object at " << std::addressof(that) << '\n' ;
}
~X()
{ std::cout << "X::destructor: id == " << id << " object at " << this << '\n' ; }
X& operator= ( const X& ) = default ;
X& operator= ( X&& ) = default ;
int id ;
std::string text = "id: " + std::to_string(id) ;
};
X foo()
{
return X(2) ; // mandatory copy elision
// https://en.cppreference.com/w/cpp/language/copy_elision
}
X bar()
{
X temp(3) ;
temp.id = 3 ;
return temp ; // permissible copy elision
// https://en.cppreference.com/w/cpp/language/copy_elision
}
int main()
{
X a(1) ; // X::constructor: id == 1 object at address of a
const X b(a) ; // X::copy_constructor: id == 1 object at address of b, copied from object at address of a
const X c = foo() ; // X::constructor: id == 2 object at address of c (mandatory copy elision)
const X d = bar() ; // X::constructor: id == 3 object at address of d (typical: permissible copy elision)
X e( std::move(a) ) ; // X::move_constructor: id == 1 object at address of e, moved from object at address of a
e.id = 99 ; // to distinguish its id from a.id
std::cout << "\n---------------\n\n" ;
// destructors called in reverse order of construction
}
L32 (in the original code) is implementation dependant (not really version dependant post C++11);
copy elision in this situation is permitted, but not mandatory.
Under the following circumstances, the compilers are permitted, but not required to omit the copy and move (since C++11) construction of class objects even if the copy/move (since C++11) constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed:
. In a return statement, when the operand is the name of a non-volatile object with automatic storage duration, which isn't a function parameter or a catch clause parameter, and which is of the same class type (ignoring cv-qualification) as the function return type. This variant of copy elision is known as NRVO, "named return value optimization".
...
Because some compilers do not perform copy elision in every situation where it is allowed (e.g., in debug mode), programs that rely on the side-effects of copy/move constructors and destructors are not portable.