4
struct Foo {
    int a_;
    Foo(): a_(0) { cout << "Default Ctor : " << a_ << endl; }
    Foo(int a): a_(a) { cout << "Ctor with parameters : " << a_ << endl; }
    Foo(const Foo& foo): a_(foo.a_) { cout << "Copy Ctor : " << a_ << endl; }
    ~Foo() { cout << "Dtor : " << a_ << endl; }
};

Foo min(Foo f1, Foo f2) { 
    return f1.a_ < f2.a_ ? f1 : f2;
}

int main(){
    Foo f1(1), f2(2), f3;
    f3 = min(f1, f2);
    return 0;
}

// Output :
//   Ctor with parameters : 1
//   Ctor with parameters : 2
//   Default Ctor : 0
//   Copy Ctor : 1
//   Copy Ctor : 2
//   Copy Ctor : 1
//   Dtor : 1  <- confused about the order here, wondering why the order is not 2 -> 1 -> 1
//   Dtor : 2
//   Dtor : 1
//   Dtor : 1
//   Dtor : 2
//   Dtor : 1

I am confused about why the return object from the min function is destructed before two local Foo objects. If the return object is destructed even before the min function ends, how can f3 be assigned?

YSYS
  • 43
  • 3
  • It is destroyed immediately after `f3 = min(f1, f2);` is executed. – user7860670 Apr 14 '22 at 06:23
  • 2
    You should try stepping through this with a debugger. Also an `operator=` that prints something might help. – Artyer Apr 14 '22 at 06:25
  • @user7860670 Doesn't the assignment f3 = XXX happen after min function ends? – YSYS Apr 14 '22 at 06:26
  • @YSYS Temporaries exist until end of full expression. In your `f3 = min(f1, f2);`, that's at the semicolon (sloppy speaking). – Scheff's Cat Apr 14 '22 at 06:27
  • @Scheff'sCat he means one temporary created by expression `min(f1, f2);`. Class doesn't have move semantic, got user-defined specials so a copy happens. – Swift - Friday Pie Apr 14 '22 at 06:29
  • It looks a bit different on [coliru](http://coliru.stacked-crooked.com/a/9f6b88d545ac3f17) ;-) @YSYS Maybe, you should [edit] in your platform (OS, compiler). – Scheff's Cat Apr 14 '22 at 06:30
  • @Artyer Nice idea: [coliru](http://coliru.stacked-crooked.com/a/5d3769685061e31d) – Scheff's Cat Apr 14 '22 at 06:33
  • I believe in the call of `min()` there are copy-constructed three instances: the two arguments `f1, f2` (in implementation defined order - according to the standard) and the temporary for the return value. After the assignment they are destructed in the reverse order of construction (how I would've expected). – Scheff's Cat Apr 14 '22 at 06:35
  • @Scheff'sCat Thanks! I wrongly think that the assignment will only occur after right-side function ends so that two arguments should be destructed first. – YSYS Apr 14 '22 at 06:40
  • 1
    You are actually right about assignment occurring after right-side function ends. However it is wrong to think that function arguments are to be destroyed prior to assignment. – user7860670 Apr 14 '22 at 06:49
  • 1
    Does this answer your question? [Destructor of a function argument being called differently in gcc and MSVC](https://stackoverflow.com/questions/37155404/destructor-of-a-function-argument-being-called-differently-in-gcc-and-msvc) – apple apple Apr 14 '22 at 06:53

1 Answers1

2

In graphical way, those output lines are corresponding to creation and deletion o temporaries and local variables as shown below:

g++ prog.cc -Wall -Wextra

Ctor with parameters : 1   - Foo f1(1)
Ctor with parameters : 2   - Foo f2(2)
Default Ctor : 0           - Foo f3  
Copy Ctor : 2              - min(Foo f1,
Copy Ctor : 1              - min(        Foo f2)
Copy Ctor : 1              - return  f1.a_ < f2.a_ ? f1 : f2;
Dtor : 1                   - f1.a_ < f2.a_ ? f1 : f2;
Dtor : 1                   - min(Foo f1,
Dtor : 2                   - min(        Foo f2)
Dtor : 1                   - f3               
Dtor : 2                   - f2
Dtor : 1                   - f1
  • Function arguments are local variables. As you defined them as values, they are getting initialized by copy constructor. They are getting destroyed on exit from function in example above (implementation defined).

  • Results of expressions are temporary objects. Ternary expression materializes only one of two.

Variations may happen due copy elision or due implementation, e.g. ternary may elide creation of temporary in this context or function could be inlined, which results in function parameters to be destroyed at different time. Hence in standards it's implementation-defined, whether it happens on exit from or on at the end of the enclosing full-expression.

Ctor with parameters : 1   - Foo f1(1)
Ctor with parameters : 2   - Foo f2(2)
Default Ctor : 0           - Foo f3  
Copy Ctor : 2              - min(Foo f1,
Copy Ctor : 1              - min(        Foo f2)
Copy Ctor : 1              - return  f1.a_ < f2.a_ ? f1 : f2;
Dtor : 1                   - min(Foo f1,
Dtor : 2                   - min(        Foo f2)
Dtor : 1                   - min(f1, f2) (f1.a_ < f2.a_ ? f1 : f2)
Dtor : 1                   - f3             
Dtor : 2                   - f2
Dtor : 1                   - f1
Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42
  • is there reason `min::f1` and `min::f2` not destroyed before the the assignment? isn't they local to `min`? – apple apple Apr 14 '22 at 06:45
  • 1
    "_They are getting destroyed on exit from function._": It is implementation-defined whether that is the case: https://www.eel.is/c++draft/expr.compound#expr.call-7.sentence-10 – user17732522 Apr 14 '22 at 06:46
  • @user17732522 ah, valid point. Deprecated implicit constexpressions and inlining might be at work there too. – Swift - Friday Pie Apr 14 '22 at 07:07