To elaborate from what by now can already be found in the comments:
Given int f(Logger);
, when you write:
f(10);
this (conceptually) constructs a temporary Logger
object, constructs the function parameter from that temporary object, calls the function, destroys the function parameter, and finally destroys the temporary object.
When you write:
f(f(10));
this (conceptually) constructs a temporary Logger
object, constructs the function parameter from that temporary object, calls the function, destroys the function parameter, constructs a new temporary Logger
object using the first function call's result, constructs the function parameter from that temporary object, calls the function, destroys the function parameter, and finally destroys the two temporary objects.
I'll avoid writing it out for f(f(f(10)));
case.
Now, those two temporary objects can be omitted:
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):
Since the function parameter and the temporary object have the same type, a compiler is allowed to treat them as the same object. The temporary object would be destroyed at the final stage, so the lifetime of the parameter does not come into play.
However, when copy elision isn't performed, for example because you configure the compiler not to, or because there is no copy to elide in the first place (see below), then the function parameters must indeed be destroyed when you say they should be, and you must see "Destruct (...)" before the second function invocation starts in all conforming C++11 implementations.
A parameter can be constructed without a temporary by using braces: you could re-work the call as
f({f({f({10})})});
Here, each parameter is list-initialised, which in this case does not involve temporary objects, and there are no copies to elide. This must destroy the function parameters as soon as the function f
returns, before f
is called again, in all C++11-conforming implementations, regardless of any -felide-constructors
command-line options, and the fact that compilers do not do this is an area in which they fail to conform to C++11.
It's not quite as simple as that, though: CWG issue 1880 reads:
WG decided to make it unspecified whether parameter objects are destroyed immediately following the call or at the end of the full-expression to which the call belongs.
This would allow exactly what compilers do now: the parameters can be destroyed after the end of the full-expression, after the last f
has returned. The exact literal text of C++11 isn't what current compilers implement.