Repeat: a temporary object would be constructed.
The anonymous temporary is a different object; an object of type GString is required for the rhs of the assignment operator; there is an implicit conversion from const char* to GString via the converting constructor.
#include <iostream>
#include <string>
#include <iomanip>
struct gstring
{
gstring() : name("") { show( "default constructed" ) ; }
gstring( constchar* name ) : name(name) { show( "constructed" ) ; }
~gstring() { show("destroyed") ; }
gstring& operator= ( const gstring& that )
{
v = that.v ;
show( "assigned to " ) ;
std::cout << " note: rhs of assignment is gstring at address " << std::addressof(that) << '\n' ;
return *this ;
}
const std::string name ;
int v = 0 ;
void show( constchar* what ) const
{
staticint cnt = 0 ;
std::cout << "step #" << ++cnt << ". gstring " << std::quoted(name)
<< " at address " << this << ' ' << what << '\n' ;
}
};
int main()
{
static gstring str( "a string" ) ;
{
std::cout << "\n*** assign const char*\n" ;
str = "anonymous temporary" ;
}
std::cout << "*** after assign (temporary object destroyed)\n\n" ;
}
step #1. gstring "a string" at address 0x602e00 constructed
*** assign const char*
step #2. gstring "anonymous temporary" at address 0x7fff6bb7ef58 constructed
step #3. gstring "a string" at address 0x602e00 assigned to
note: rhs of assignment is gstring at address 0x7fff6bb7ef58
step #4. gstring "anonymous temporary" at address 0x7fff6bb7ef58 destroyed
*** after assign (temporary object destroyed)
step #5. gstring "a string" at address 0x602e00 destroyed
When I start the program in Release mode (Visual C++) nothing happens.
If I switch to Debug mode, that message appears. Scary...
#### by the way ####
This is interesting. Listen up.
When I return result from the function, it's gonna be put inside the COUT statement which will print its mainString variable ( as defined with the operator<< )
When I return the temporary object result the Move Constructor of my class gets, as expected, called:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
GString::GString(GString&& move)
{
// this-> can be omitted, I know, but: who is it pointing to?this->size = move.size;
this->mainString = newchar[size];
int i = 0;
while (i < size)
{
mainString[i] = move.mainString[i];
i++;
}
mainString[i] = '\0';
// Prepare the rvalue to be destroyed
move.mainString = nullptr;
move.size = 0;
}
Where move is the variable which I'm returning: result
But the question is: who is the object pointed by the pointer THIS, in this case?
If I had something like
GString myString = GString("I'm an rvalue!");
move would be the rvalue and this would be myString.
I have updates my Move Constructor in a better way:
1 2 3 4 5 6 7 8 9 10
GString::GString(GString&& move)
{
// let's steal the resources
size = move.size;
mainString = move.mainString;
// set default values for the rvalue about to be destroyed
move.size = 0;
move.mainString = nullptr;
}
The compiler can use this information to enable certain optimizations on non-throwing functions ...
For example, containers such as std::vector will move their elements if the elements' move constructor is noexcept, and copy otherwise ... http://en.cppreference.com/w/cpp/language/noexcept_spec
When copy elision occurs, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object ...
Multiple copy elisions may be chained to eliminate multiple copies. http://en.cppreference.com/w/cpp/language/copy_elision
Illustration of the importance of the move constructor being noexcept:
GString GString::SubString(constint beginIndex, constint endIndex) const
{
// ...
GString result{ tmp };
// ...
return result; // move 'result' into the anonymous return value
// (move construct the value returned from the function)
}
Here's a small program that illustrates what happens when copy elision (NRVO) is disabled:
#include <iostream>
#include <string>
struct A
{
explicit A( std::string v ) : v(v) { std::cout << "construct " << *this << '\n' ; }
~A() { std::cout << "destroy " << *this << '\n' ; }
A( A&& that ) : v( std::move(that.v) )
{ std::cout << "move_construct " << *this << "\n\tfrom " << that << '\n' ; }
std::string v ;
friend std::ostream& operator<< ( std::ostream& stm, const A& a )
{ return stm << "A{" << a.v << "} at address " << std::addressof(a) ; }
};
A bar()
{
std::cout << "****** in foo ************\n" ;
A result( "bar_result_123456789" ) ; // long enough to disable small string optimisation
return std::move(result) ; // disable copy elision
}
int main()
{
A&& a = bar() ;
std::cout << "\n****** back in main ************\n" ;
std::cout << a << '\n' ;
}
****** in foo ************
construct A{bar_result_123456789} at address 0x7fff5439b090
move_construct A{bar_result_123456789} at address 0x7fff5439b060
from A{} at address 0x7fff5439b090
destroy A{} at address 0x7fff5439b090
****** back in main ************
A{bar_result_123456789} at address 0x7fff5439b060
destroy A{bar_result_123456789} at address 0x7fff5439b060
> I have not disabled the copy-elision, but still the Move Constructor gets called.
Copy elision is permitted, but not mandated by the IS.
Some compilers do not always enable this optimisation (for instance the Microsoft compiler, in debug mode builds).
There is a proposal for guaranteed copy elision; but it does not address NRVO.
ISO C++ permits copies to be elided in a number of cases:
. when a temporary object is used to initialize another object (including the object returned by a function, or the exception object created by a throw-expression)
. when a variable that is about to go out of scope is returned or thrown
. when an exception is caught by value
Whether copies are elided in the above cases is up to the whim of the implementation. In practice, implementations always elide copies in the first case, but source code cannot rely on this, and must provide a copy or move operation for such cases, even when they know (or believe) it will never be called.
This paper addresses only the first case. While we believe that reliable NRVO ("named return value optimization", the second bullet) is an important feature to allow reasoning about performance, the cases where NRVO is possible are subtle and a simple guarantee is difficult to give.
> So in GCC I should see copy elision, as you said?
Yes. Or, if you are using Visual Studio, with the clang++ front-end - 'Clang 3.7 with Microsoft CodeGen'
> Because now, with VC++, I'm having troubles... I think I meet all the requirements for a copy elision to happen
Yes. The Microsoft compiler (with optimisations enabled) consistently elides copies for RVO; but not for NRVO.
RVO:
1 2 3 4 5 6 7 8 9 10 11 12
A bar()
{
std::cout << "****** in foo ************\n" ;
return A( "bar_result_123456789" ) ; // test RVO
}
int main()
{
A&& a = bar() ;
std::cout << "\n****** back in main ************\n" ;
std::cout << a << '\n' ;
}
****** in foo ************
construct A{bar_result_123456789} at address 0044FB70
****** back in main ************
A{bar_result_123456789} at address 0044FB70
destroy A{bar_result_123456789} at address 0044FB70
A bar()
{
std::cout << "****** in foo ************\n" ;
A result( "bar_result_123456789" ) ; // long enough to disable small string optimisation
return result ; // test NRVO
}
int main()
{
A&& a = bar() ;
std::cout << "\n****** back in main ************\n" ;
std::cout << a << '\n' ;
}
****** in foo ************
construct A{bar_result_123456789} at address 006FFB50
move_construct A{bar_result_123456789} at address 006FFB8C
from A{} at address 006FFB50
destroy A{} at address 006FFB50
****** back in main ************
A{bar_result_123456789} at address 006FFB8C
destroy A{bar_result_123456789} at address 006FFB8C
****** in foo ************
construct A{bar_result_123456789} at address 00E6FB58
****** back in main ************
A{bar_result_123456789} at address 00E6FB58
destroy A{bar_result_123456789} at address 00E6FB58