2

So, code first:

#include <iostream>
#include <utility>

struct X{
    int i;
    void transform(){}
    X() :i(0){std::cout<<"default\n";}
    X(const X& src): i(src.i){std::cout<<"copy\n";}
    X(X&& msrc) :i(msrc.i){msrc.i=0;std::cout<<"move\n";}
};

X getTransform(const X& src){
    X tx(src);
    tx.transform();
    return tx;
}

int main(){

    X x1;// default
    X x2(x1); // copy
    X x3{std::move(X{})}; // default then move
    X x41(getTransform(x2)); // copy in function ,then what?
    X x42(std::move(getTransform(x2))); // copy in funciton, then move
    X x51( (X()) );//default, then move? or copy?
      // extra() for the most vexing problem
    X x52(std::move(X())); //default then move
    std::cout<<&x41<<"\t"<<&x51<<std::endl;
}

Then ouput from cygwin + gcc 4.8.2 with C++11 features turned on:

default
copy
default
move
copy
copy
move
default
default
move
0x22aa70        0x22aa50

What I don't quite get is the line for x41 and x51. For x41, should the temporary returned from the function call invoke the move constructor or the copy? Same question for x51.
A second question is that, by looking at the output, the constructions of x41 and x51 didn't call any constructors defined, but the objects are clearly created as they reside in memory. How could this be?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
CloudyTrees
  • 711
  • 2
  • 10
  • 20
  • An unnamed object matches `&&` better that `const&`, naturally. Otherwise move semantics would not work. Still, if it is returned from a function and directly used to initialize an object of the same type, that move will be omitted. – Deduplicator Jun 11 '14 at 20:51
  • 1
    Interesting. Looks like that g++ version has to work on their move semantics a bit more: Elliding copies is better than just doing a move. – Deduplicator Jun 11 '14 at 21:07
  • 2
    possible duplicate of [What are copy elision and return value optimization?](http://stackoverflow.com/questions/12953127/what-are-copy-elision-and-return-value-optimization) – Ali Jun 11 '14 at 21:09

3 Answers3

6

An unnamed object matches && better than const&, naturally.
Otherwise move semantics would not work.

Now, there are fewer calls to your default/copy/move-constructors, then one might naively expect, because there's a special rule allowing the ellision of copies, without regard to observable behavior (which must otherwise be preserved by optimizations):

12.8 Copying and moving of objects § 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. 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):
Still, if it is returned from a function and directly used to initialize an object of the same type, that move will be omitted.
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.
- [... 2 more for exception handling]

So, going through your list:

X x1;// default
// That's right
X x2(x1); // copy
// Dito
X x3{std::move(X{})}; // default then move
// Yes. Sometimes it does not pay to call `std::move`
X x41(getTransform(x2)); // copy in function ,then what?
// Copy in function, copy to output, move-construction to x41.
// RVO applies => no copy to output, and no dtor call for auto variable in function
// Copy ellision applies => no move-construction nor dtor of temporary in main
// So, only one time copy-ctor left
X x42(std::move(getTransform(x2))); // copy in funciton, then move
// `std::`move` is bad again
X x51( (X()) );//default, then move? or copy? // extra() for the most vexing problem
// Copy-elision applies: default+move+dtor of temporary
// will be optimized to just default
X x52(std::move(X())); //default then move
// And again `std::`move` is a pessimization

I thought using static_cast might avoid binding the temporary, meaning the move can be ellided, but no such luck: 1376. static_cast of temporary to rvalue reference Thanks @dyp for unearthing this issue.

Community
  • 1
  • 1
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • So, to my understanding, if my compiler optimized away the move for x3, there should not be any moves in x42 and x52, correct? Thank you! – CloudyTrees Jun 11 '14 at 21:18
  • 2
    *"If your compiler is any good, the move will be omitted"* Only if it has no side-effects (i.e. not for the type `X`). The temporary is bound to the reference parameter of `std::move`, this prohibits copy/move elision. – dyp Jun 11 '14 at 21:32
  • Nice explanation especially the quote from the standard I like the bold parts. – 101010 Jun 11 '14 at 21:38
  • @dyp: Corrected that. It's a pity that calls to `std::move` cannot be reduced to just changing the type without disabling those optimizations... – Deduplicator Jun 11 '14 at 21:39
  • @Deduplicator I wonder if `static_cast(..)` is different.. I remember having read about that in the issues list, but maybe relating to lifetime extensions. – dyp Jun 11 '14 at 21:41
  • 1
    [Here it is](http://www.open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1376), I think it suggests `static_cast(..)` will also prevent move elision. – dyp Jun 11 '14 at 21:43
  • @dyp: Tested, does not do so with g++ at least. So, they fixed the builtin casts semantics? Good to hear. – Deduplicator Jun 11 '14 at 21:49
  • @Deduplicator ("does not do so" == no move elision) or ("does not do so" == does not prevent move elision)? – dyp Jun 11 '14 at 21:54
  • @dyp: Ah, the move was ellided when using `static_cast` directly instead of `std::move`. Sorry for being unclear. Now if they only fix `std::move` and `std::forward` so they don't count for binding of temporaries... – Deduplicator Jun 11 '14 at 21:55
  • @Deduplicator Well that's interesting. – dyp Jun 11 '14 at 21:59
  • I would interpret the proposed resolution in the opposite sense: The lifetime will *not* be extended because `static_cast(..)` *binds to a reference*. Note that [clang++ behaves differently](http://coliru.stacked-crooked.com/a/2a22627db251c3ea). I'll try to find out more about this issue or I'll ask a question. – dyp Jun 11 '14 at 22:10
1

I think it's simply Return Value Optimization kicking in. You create a copy within the functions on X tx(src);, and then this local variable is just given back to the main. Semantically as a copy, but in fact the copy operation is omitted.

As others have said, moves can also be omitted.

luk32
  • 15,812
  • 38
  • 62
  • 1
    Not quite: `tx` is not a temporary, and it will be *moved* out of the function (semantically) into the return value. NRVO applies, so the *move* will be elided. (yes, even though `tx` is an lvalue; it's a special language rule). The return value is a temporary, and will be used to construct the variable in `main`, and this *move* can be elided as well. – dyp Jun 11 '14 at 20:55
  • @dyp I don't think there is any difference between move and copy when it comes to elision. But could you please explain how `ts` is not a temporary? It's scope is limited to the `getTransform` is it incorrect to call it a temporary in such a context? I can edit the answer, so it will say "the `tx` upon the end of its life time gets semantically coppied, but the actual copy operation is omitted". Does it sound better/more correct? – luk32 Jun 11 '14 at 21:23
  • A temporary, as per the C++ Standard, is an unnamed object that is destroyed at the end of the (full)expression in which it has been created. `tx` inside `getTransform` is just a local variable. -- Of course, there's not much difference between an elided move and an elided copy. However, if NRVO is allowed but not performed, `tx` will be moved into the return value. – dyp Jun 11 '14 at 21:30
1

According to the 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.124 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.

  • 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

Thus in the both cases (i.e., x41, x51 respectively) you are experiencing a copy elision optimization effect.

101010
  • 41,839
  • 11
  • 94
  • 168