After reading about copy elision of cppreference I wanted to play with exceptions and copy elision. So I wrote the following code with gcc 7.2 on coliru
#include <iostream>
class Exception
{
public:
Exception() { std::cout << "constructed\n"; }
Exception(Exception& other) {std::cout << "copy constructed\n";}
Exception(const Exception& other) {std::cout << "copy constructed\n";}
Exception(Exception&& other) {std::cout << "move constructed\n";}
};
void foo()
{
throw Exception();
}
int main()
{
try
{
foo();
}
catch(Exception e )
{
}
}
Output
constructed
copy constructed
We can see that the copy constructor is called and this happens even when gcc is invoked with -O2. It seems to me that this code should be eligible to copy elision according to the following clause :
When handling an exception, if the argument of the catch clause is of the same type (ignoring top-level cv-qualification) as the exception object thrown, the copy is omitted and the body of the catch clause accesses the exception object directly, as if caught by reference.
So why is the copy constructor called? Why does copy elision not work in this case?
Copy elision is an optimization implemented by most compilers to prevent extra (potentially expensive) copies in certain situations. It makes returning by value or pass-by-value feasible in practice (restrictions apply).
Copy elision (NRVO) is allowed there and is routinely performed by most compilers, but is still non-guaranteed, and the widget class cannot be non-copyable non-movable.
It is in this sense that computer language designers have borrowed the term: an elision in a programming language is when the language makes optional what might be thought of as a necessary portion of the grammar.
In the context of the C++ programming language, return value optimization (RVO) is a compiler optimization that involves eliminating the temporary object created to hold a function's return value. RVO is allowed to change the observable behaviour of the resulting program by the C++ standard.
cppreference is inaccurate on this. There is in fact no guarantee that the copy will be elided except in a constant expression (constexpr
) or a constant initialization (static or thread-local). That is not the case in your example.
See [class.copy.elision]/1 in the current C++17 draft (emphasis mine):
When certain criteria are met, ... elision of copy/move operations, called copy elision, is permitted in the following circumstances:
— in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object can be omitted by constructing the automatic object directly into the exception object
Copy elision is required where an expression is evaluated in a context requiring a constant expression and in constant initialization.
That means that some day it may be implemented as a compiler optimization, it is just not the case currently.
It would be wise therefore to catch
by const&
for the time being (also to avoid accidental slicing).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With