4

From cppreference,

When copy elision occurs, the implementation treats the source and target of the omitted copy/move (since C++11) 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 (except that, if the parameter of the selected constructor is an rvalue reference to object type, the destruction occurs when the target would have been destroyed) (since C++17).

For a simple case like A a = returnA();, I can understand that the object is not destroyed in returnA() and instead the destruction occurs as in the case A a; which is the later time.

I can't think of a case which the opposite happens such that the source of the copy/move operation is destroyed first. Also I would like an example of the added statement since C++17 (exception when parameter of selected constructor is an rvalue reference to object type)

jaggedSpire
  • 4,423
  • 2
  • 26
  • 52
pterodragon
  • 429
  • 8
  • 16
  • 1
    So... what exactly is your question here? – Nicol Bolas Dec 13 '18 at 14:53
  • 1
    There are two questions: in which case the object created in `returnA` could outlive the `a` it is "created" into? and the second question is: what does mean the provision added in C++17: `(except that, if the parameter of the selected constructor is an rvalue reference to object type, the destruction occurs when the target would have been destroyed)` – papagaga Dec 13 '18 at 16:25
  • 1
    "*in which case the object created in returnA could outlive the a it is "created" into?*" I'm not sure I understand the question. The quoted paragraph doesn't say that there are such cases. – Nicol Bolas Dec 13 '18 at 17:01
  • Could help : https://en.cppreference.com/w/cpp/language/value_category – Oliv Dec 13 '18 at 20:48
  • Do you mean "source" or "target"? – T.C. Dec 13 '18 at 21:59
  • @Nicol Bolas "in which case the object created in returnA could outlive the a it is "created" into?" My first question is exactly this – pterodragon Dec 13 '18 at 23:19
  • @T.C. Doesn't "source" mean the object that would have been created in `returnA` without copy elision and "target" means the `a` in `A a = returnA();`? – pterodragon Dec 13 '18 at 23:25
  • @Nicol Bolas The paragraph doesn't say there can be cases where the object created in `returnA` could outlive the `a`it is "created" into. But why does it say "destruction of that object occurs at the **later of the times when the two** objects would have been destroyed without the optimization"? – pterodragon Dec 13 '18 at 23:29

1 Answers1

3

The symmetric case where the source outlives the target is when the prvalue is a parameter:

struct A {
  static int *data;
  A() {if(!refs++) data=new int(42);}
  A(const A&) {++refs;}  // not movable
  ~A() {if(!--refs) delete data;}
private:
  static int refs;
};
int A::refs,*A::data;
int* f(A) {return A::data;}
A returnA();
int returnInt() {return *f(returnA());} // ok

Because the result of returnA() is a temporary, its lifetime extends to the end of the return statement’s full-expression. The implementation may identify it with f’s parameter, but may not destroy it when f returns, so the dereference in returnInt is valid. (Note that parameters may survive that long anyway.)

The adjustment in C++17 (along with such elision being guaranteed) is that if you (would) move the prvalue, it may be destroyed when the parameter is (since you shouldn’t be relying on its contents anyway). If that’s when f returns, the (ill-advised) code above becomes invalid if A is made movable.

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • I swapped the copy constructor with a move constructor, and changed the `returnInt` to `int returnInt() {return *f(move(returnA()));}`. That results in a call sequence `A(), A(const A&&), ~A(), ~A()`. Is that what the C++17 adjustment is about? – pterodragon Dec 15 '18 at 05:57
  • That merely demonstrates that calling `move` on a prvalue forces a temporary to be materialized. (In C++14 terms, it prevents the optional copy/move elision.) – Davis Herring Dec 15 '18 at 07:00
  • But when I swapped the copy constructor with a move constructor only (and added visible printf side effect in it), I don't see that the move constructor gets called either (with gcc-8). What do you mean by "the (ill-advised) code above becomes invalid if A is made movable"? – pterodragon Dec 15 '18 at 09:51
  • 1
    @pterodragon: The move constructor isn’t called, but its *selection* **allows** the argument/parameter to be destroyed (destroying `data`) before `data` is dereferenced. – Davis Herring Dec 15 '18 at 15:29
  • I presume the definition of `returnA()` is `A returnA() { {}; }`? – jonspaceharper Dec 15 '18 at 21:26
  • @JonHarper: Does its body matter? The argument depends only on there being no *other* `A` objects (and that only because of the `static` variables used as an example). – Davis Herring Dec 15 '18 at 22:24
  • Not particularly. Just being thorough that I understood your answer. – jonspaceharper Dec 16 '18 at 09:19