Consider the following toy code:
class X {};
class Y {
public:
Y() { cout << "Y ctor\n"; }
~Y() { cout << "Y dtor\n"; }
};
int gun() {
throw X{};
return 42;
}
void fun(Y yy, int i) {}
int main()
{
Y a;
cout << "--------\n";
try
{
fun(a, gun());
}
catch (const X&)
{
cout << "catched\n";
}
cout << "--------\n";
}
The outputs of this are as follows:
Y ctor
--------
catched
--------
Y dtor
May I ask why the destructor of the parameter yy
(copy initialized by a
) is not called?
Same outputs are produced when I switch the order of the arguments (i.e. using void fun(int i, Y yy) {}
and fun(gun(), a);
), so I don't think it is because of the undefined order of evaluation of function arguments.
Update: see demo
CodePudding user response:
There are no sequencing rules between the argument expressions in a function call and/or the initialization of function parameters until C 17. Since C 17 there are some rules which however still do not establish any ordering between the individual arguments.
As a consequence only the indeterminate sequencing rules for function calls apply and the compiler is free to choose to either call gun()
first and construct Y yy
afterwards or the other way around. Due to the indeterminate sequencing rules the compiler is however not allowed to interleave these calls.
So, if the compiler chooses the former order of evaluation, Y yy
's constructor is never called before the exception is thrown and hence its destructor also won't (and shouldn't) be called.
If the compiler decides to do it the other way around, then Y yy
will have been constructed when the exception is thrown and its destructor will then also be called.
You can verify this by adding output in the copy constructor as well. You will see that either the copy constructor is never called and yy
does not have a destructor called or both will be called.
You cannot control which of the two scenarios happens. The choice is unspecified, meaning that the compiler could also just choose it randomly at runtime. Reordering the arguments will not help. In practice there will be some rules in the compiler to decide the order at compile-time, probably in an order that is beneficial to optimization. It may be inconsistent between multiple equivalent calls as well.