4

After a code review we had an issue with copy elison in try/catch block. After reading this page : cpp reference guide and particularly this paragraph :

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

I thought that copy elision for the argument in the catch will be performed automatically but one of the reviewer run a simple test showing that copy elision was not performed by the compiler :

#include <iostream>

class A
{
public:
   A(){}
   A(const A&){
    std::cout<<"COPY CONSTRUCTOR\n";
   }
};

int main()
{
    try {
       throw A{};
    } catch(A a) {
       throw a;
    }
    return 0;
 }

When compiling with :

g++ a.cpp -std=c++11 -O3

I got the following output

COPY CONSTRUCTOR
COPY CONSTRUCTOR
terminate called after throwing an instance of 'A'
Aborted (core dumped)

I was expecting an output similar to (only one call of the copy constructor when the exception is thrown) :

COPY CONSTRUCTOR
terminate called after throwing an instance of 'A'
Aborted (core dumped)

The test has been run under Linux Ubuntu 16.04 with g++ version :

 g++ (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609

Is the test case invalid or my understanding of the copy elision wrong ? Thank you very much for your help

Community
  • 1
  • 1
clam37
  • 143
  • 9
  • 5
    The reference does state: *Under the following circumstances, the compilers are permitted to omit the copy- and move- (since C++11)constructors of class objects even if copy/move (since C++11) constructor and the destructor have observable side-effects.*. Just because it is permitted doesn't mean it will happen. – NathanOliver Mar 07 '17 at 16:11
  • 1
    I don't care much for the copy (it's an exceptional situation anyway), but `catch(A a)` just *looks* wrong, because catching by const reference has been so firmly established in the world of C++. Not to mention the danger of object slicing. – Christian Hackl Mar 07 '17 at 16:28
  • The std clause is http://eel.is/c++draft/class.copy#elision-1.3 for an authoritative ref – Cubbi Mar 07 '17 at 17:16

1 Answers1

3

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 ...

The wording on cppreference is overly strong. This is a list of cases where copy elision is permitted. That should read: "The copy can be omitted".

It appears that the compiler didn't perform copy elision, even though it was permitted to.


Relevant quotes from the standard (draft):

[class.copy.elision] / 1

... This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

[class.copy.elision] / 1.3

when the exception-declaration of an exception handler (Clause [except]) declares an object of the same type (except for cv-qualification) as the exception object, the copy operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration. [ Note: There cannot be a move from the exception object because it is always an lvalue.  — end note ]

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • By side effect you mean , the printing of the message "COPY CONSTRUCTOR" ? – clam37 Mar 07 '17 at 16:39
  • but then how to test the copy elision, we have to rely on the specification ? – clam37 Mar 07 '17 at 16:42
  • 1
    It says "other than". Skipping the side-effectful copy constructors is the whole point of copy elision. – Cubbi Mar 07 '17 at 17:04
  • @Cubbi So, copy elision is the omission of copy even if copy ctor or dtor had observable side effects. And copy elision is permitted in the argument of the catch clause if the type matches, but only if there are no observable side effects, other than those from the copy ctor or dtor. Isn't that redundant? What changes to side effects could there be beside those that were already allowed by copy elision explicitly? I (mis?) interpreted this as an exception to what copy elision normally allows. Is it really just re-stating that changes to observable effects of copy ctor and dtor are allowed? – eerorika Mar 07 '17 at 17:29
  • Good question. Cppreference is just repeating what the standard says in that clause. I think it refers to modifying the exception object: you can update and rethrow (parameterless), but if you caught it by value, update should not affect the one that's rethrown – Cubbi Mar 07 '17 at 17:40
  • 1
    @Cubbi OK, I changed the answer to match that. However, GCC seems to behave very much the way I interpreted: With the side-effects of copy ctor, it does not elide the copy (even though seemingly permitted). Without it generates identical assembly if caught by reference or by value (elided). Still, that behaviour isn't against this interpretation of the standard either. – eerorika Mar 07 '17 at 17:47