4

I have been testing with rvalue references and move semantics and want to make sure I understand when a copy should be elided and when it should follow move semantics.

Given the following

class NRVCA
{
public:
    NRVCA(int x):
    {}
    NRVCA(const NRVCA & Rhs)
    {}    
    NRVCA& operator=(const NRVCA& dref)
    {}          
};

NVCRA GetATemp()
{       
   return NVCRA(5);
} 

NVCRA GetACopy()
{
   NVCRA ret(5);
   ...
   return ret;  
}

int main()
{ 
    //This call will be elided allays and invoke the single param constructor 
    NVCRA A = GetATemp();
    //This call will be a traditional copy the complier may elide this 
    // if so the work will be done inline 
    NVCRA B = GetACopy();

}

In this case move semantics play no part and the only difference from C++03 in c++11 is that rather than the compiler being allowed to elide they are required to elide.

So Question 1. Under what cases am I guaranteed a copy constructor will or will not be elided.

Question 2. Is there a way to force the compiler to not elide.

Question 3. Is there any logical reason why I would not want the compiler to do so, assuming that you have logically consistent copy operations.

Question 4. If I define a move constructor, a move will occur in cases where the copy is not elided anyway. Should this effect my design of classes.

ash
  • 3,354
  • 5
  • 26
  • 33
rerun
  • 25,014
  • 6
  • 48
  • 78

3 Answers3

7
  1. Eliding copies and moves is always an optional optimization and the standard offers no guarantees on when it will be done. Elision is different from the compiler choosing move over copy. The language guarantees when move construction or move assignment is chosen according to its normal overload resolution rules.

  2. Some compilers offer a flag to turn elision off. gcc and clang have -fno-elide-constructors. MSVC does not have a specific option, but disabling optimization may avoid some elisions (but some can't be turned off no matter what, such as the copy in Foo x = 1;)

  3. I don't know of any reason not to elide copies/moves in production builds.

  4. Some people had recommended not relying on the 'return value optimizaiton' when returning 'heavy' classes, because RVO isn't guaranteed. Personally I just verified that my compilers were good about it and went ahead with it. Now that objects can be moved you no longer need to worry about whether a compiler supports such optimizations, because even if it doesn't you'll still get moves instead of copies.

bames53
  • 86,085
  • 15
  • 179
  • 244
  • Note that moves instead of copies doesn't get you much if the object is "flat", i.e. has no pointers. You'll just get a plain-old copy then. – Xeo Feb 13 '12 at 22:15
  • Yes, as Herb Sutter says, think of move as an optimization of copy. There's no point in even implementing a move ctor or move assignment operator for types where it won't be faster than copying. That means moving is really only for 'heavy' objects, which is the same place people might have had to depend on RVO before. – bames53 Feb 13 '12 at 22:33
4

Quoting the C++11 standard (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.", so:

  1. Copy elision is not guaranteed. (technically; but compiler vendors are motivated to so for marketing reasons)
  2. This is a compiler-specific question.
  3. Technically there are some cases (see here) but they would indicate (IMO) some error in the design.
  4. You should still provide move constructor (if it is possible to implement it effectively). There are some cases where copy elision is prohibited but move constructor is just fine:

    vector<string> reverse(vector<string> vec)
    {
        reverse( vec.begin(), vec.end() );
        return vec;
    }
    
    auto vec = reverse(vector<string>{"abc", "def", "ghi"});
    

Copy elision is explicitly not allowed to propagate from function argument to the final automatic value initialized from a temporary. But no copy is called due to move constructor.


To be more specific about my last remark, I quote N3290, which is difficult to get nowadays, but it is very close to N3337:

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. 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):

-- 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

12.8/32:

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, 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. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [...]

Andrzej
  • 5,027
  • 27
  • 36
  • Can you cite where eliding copies from argument to return value is disallowed? I've heard some things that seemed to indicate that it was allowed, just nobody does it because it's impractical to implement. – bames53 Feb 13 '12 at 21:33
  • I added the citations. On the other hand I was never able to understand what the difficulty would be in implementing this kind of elision. Do you happen to know? – Andrzej Feb 13 '12 at 23:27
  • Note that even if copy elision is not allowed, optimizing away the copy or move may still be allowed under the as-if rule. Of course in that case, the compiler will have to prove that the removal doesn't change observable behaviour, but I guess the chances to do so for move constructors are higher than for copy constructors. – celtschk Feb 13 '12 at 23:37
  • @Andrzej Thanks. I think the issue is that the code that constructs the function's parameters needs to know that elision is going to happen so that it can construct that parameter in the location of the return value. That information is internal to the function while the parameters are constructed by the function's caller. So if the caller can see that it can possibly elide a copy (say, because it can see the function definition), the function implementation would need to not perform that copy. – bames53 Feb 13 '12 at 23:57
  • @Andrzej But if the function can't figure out that it can elide that copy (say, because it's in a different TU without access to the function definition) the the funciton implementation would need to perform that copy explicitly. – bames53 Feb 13 '12 at 23:58
2
  1. I don't believe there are any guarantees about when the compiler may choose to elide the copy, even if the copy constructor has side effects. This is explicitly allowed by the standard.

  2. Which compiler? For example, I believe you can turn off optimization and not get elision in g++. There is no way within the language to force any compiler to generate a copy constructor where it thinks elision is possible. The standard explicitly allows elision even when the copy constructors has side effects.

  3. I can't think of a reason to not elide.

  4. Copy elision shouldn't affect the logical design of your class as a general rule. Design your class first, then if you have performance problems use a profiler to help improve it.

Mark B
  • 95,107
  • 10
  • 109
  • 188
  • for 2 I meant in the language or by some convention that indicated to the compiler that I have a copy constructor that has a side effect and I need it copied. Not that that is a normal situation. – rerun Feb 13 '12 at 20:57
  • 2
    No, there's no standard way to disable elisions for a particular type for those cases where your copy-ctor has side effects. C++ is explicitly does not consider side effects when it allows elision. You should always write your copy-ctor such that it behaves well in the presence of elision, or you are likely to get undefined behavior. Basically, don't use copy or move constructors for anything except copying and moving. – bames53 Feb 13 '12 at 21:10