6

Let's say I have a function that looks like this:

SomeObject copy_maybe(bool make_new, const SomeObject& def)
{
    if (make_new)
        return SomeObject();
    else
        return def;
}

And I call it like this:

SomeObject obj;
obj = copy_maybe(true, obj);

Without copy elision, this will clearly always result in a copy to obj from a temporary created in copy_maybe. However, with copy elision/RVO, is it possible that the copy will happen from obj to obj?

More specifically, under these (or similar) conditions, is it possible in a copy operator (void operator=(SomeObject const &other)) that this and &other will be the same due to copy elision?

I created a test on Ideone, and it returns separate addresses, but I just want to make sure that this behavior is defined by the spec.

jpfx1342
  • 868
  • 8
  • 14
  • That's more or less the conclusion I came to, but I don't understand the spec well enough to make a conclusive answer. – jpfx1342 Apr 11 '18 at 17:54

3 Answers3

4

However, with copy elision/RVO, is it possible that the copy will happen from obj to obj?

No. Copy elison/RVO is used in the process of initializing a variable. Since you already initialized obj using SomeObject obj; you wont get any optimization. The copy assignment operator will be called and the obj from the call site will be assigned the value of the obj from the function.

If you had

SomeObject obj = copy_maybe(true, obj);

Then yes, copy elison can (will in C++17) come into play.


Do note that calling

SomeObject obj = copy_maybe(false, obj);

Will leave obj in an indeterminate state as it would be the same as

SomeObject obj = obj;
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • But this would cause UB, because obj hadn't been initialized yet, right? – jpfx1342 Apr 11 '18 at 18:05
  • @jpfx1342 Not if you call it with `true` as you will get a default object. I have added about what happens when you call it with false. – NathanOliver Apr 11 '18 at 18:07
  • To add to the answer, RVO cannot happen since the compiler does not know what instance will be returned, the function has two `return` expressions. Unless the function is constexpr and the `bool make_new` is known in compile time. – AdvSphere Apr 11 '18 at 21:57
2

Copy/move assignments can never be elided; elision only happens with the initialization of an object.

However, there is a way to make elision apply to an existing object:

SomeObject obj;
new(&obj) auto(copy_maybe(false, obj);

C++17 defines this in an interesting way. The placement new happens before the construction of the new object. The placement new call is said to "obtain storage" for that object.

And the standard says that when you "obtain storage" for an object, the lifetime of any objects that are already in that storage are terminated (since their storage is being reused for a new object). Therefore, the object declared initially has its lifetime ended. But it's destructor doesn't get called; if your program relies on a destructor to be called before an object's lifetime is ended, then you have UB.

But this provokes UB anyway. Why? Because obj's lifetime ended before copy_maybe is called. So copy_maybe will get a reference to an object that no longer exists. And when copy_maybe accesses a reference to a non-existent object, you get UB.

Similarly:

SomeObject obj = copy_maybe(false, obj);

This also provokes UB. The initialization of obj is non-vacuuous; therefore, its lifetime does not start until its initialization has been completed. And that happens in copy_maybe. But copy_maybe is being given a reference to an object that has not had its lifetime start. That's UB.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Though I accepted the other answer, I think this one is valuable too, since it goes into more detail about the lifetime of objects. – jpfx1342 Apr 11 '18 at 18:49
1

However, with copy elision/RVO, is it possible that the copy will happen from obj to obj?

No to "copy elision/RVO".
Yes to "copy will happen from obj to obj" but only as copy assignment.

It is advised to check for self assignment in user defined copy assignment functions. When you do that, your code should work fine.

SomeObject& operator=(SomeObject const& rhs)
{
   // Do nothing for self assignment.
   if ( this != &rhs )
   {
      ...
   }

   return *this;
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270