views:

298

answers:

2

Imagine two similar pieces of code:

try {
  [...]
} catch (myErr &err) {
  err.append("More info added to error...");
  throw err;
}

and

try {
  [...]
} catch (myErr &err) {
  err.append("More info added to error...");
  throw;
}

Are these effectively the same or do they differ in some subtle way? For example, does the first one cause a copy constructor to be run whereas perhaps the second reuses the same object to rethrow it?

+12  A: 

In the second case according to C++ Standard 15.1/6 copy constructor is not used:

A throw-expression with no operand rethrows the exception being handled. The exception is reactivated with the existing temporary; no new temporary exception object is created. The exception is no longer considered to be caught; therefore, the value of uncaught_exception() will again be true.

In the first case new exception will be thrown according to 15.1/3:

A throw-expression initializes a temporary object, called the exception object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively. <...> The temporary is used to initialize the variable named in the matching handler (15.3). The type of the throw-expression shall not be an incomplete type, or a pointer or reference to an incomplete type, other than void*, const void*, volatile void*, or const volatile void*. Except for these restrictions and the restrictions on type matching mentioned in 15.3, the operand of throw is treated exactly as a function argument in a call (5.2.2) or the operand of a return statement.

In both cases copy constructor is required at throw stage (15.1/5):

When the thrown object is a class object, and the copy constructor used to initialize the temporary copy is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated). Similarly, if the destructor for that object is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated).

Kirill V. Lyadvinsky
Since the copy ctor needs to be accessible for the originally thrown exception anyway, I don't think that's an issue in this case. But yours is still a good answer. +1
sbi
Yes, but in the first case copy c-tor will be used twice.
Kirill V. Lyadvinsky
It says when the declaration specifies a class type. However in his case it specifies a reference type :) The reference will be bound directly and refer to the exception object. So, we will require a copy ctor only at the point of throwing, not catching in this case (It would make a difference when a base copy constructor is protected, for example).
Johannes Schaub - litb
I missed that he is using reference type in exception-declaration. Fixed.
Kirill V. Lyadvinsky
+8  A: 

Depending on how you have arranged your exception hierarchy, re-throwing an exception by naming the exception variable in the throw statement may slice the original exception object.

A no-argument throw expression will throw the current exception object preserving its dynamic type, whereas a throw expression with an argument will throw a new exception based on the static type of the argument to throw.

E.g.

int main()
{
    try
    {
        try
        {
            throw Derived();
        }
        catch (Base& b)
        {
            std::cout << "Caught a reference to base\n";
            b.print(std::cout);
            throw b;
        }
    }
    catch (Base& b)
    {
        std::cout << "Caught a reference to base\n";
        b.print(std::cout);
    }

    return 0;
}

As written above, the program will output:

Caught a reference to base
Derived
Caught a reference to base
Base

If the throw b is replace with a throw, then the outer catch will also catch the originally thrown Derived exception. This still holds if the inner class catches the Base exception by value instead of by reference - although naturally this would mean that the original exception object cannot be modified, so any changes to b would not be reflected in the Derived exception caught by the outer block.

Charles Bailey
Ah, I completely forgot about slicing! Dammit, that's important! Thanks for bringing this up. +1 (Although I think when you wrote "...preserving the original static type..." you meant _dynamic_ type. What's called _dynamic type_, after all, if not the _"original static type"_.) -
sbi
Great answer, I completely forgot about that too.
GMan
I'm happy someone else has run into the _slicing_ problem ;)
D.Shawley
+1 for mentioning slicing. But "...preserving the original static type..." -- Don't you mean dynamic type?
sellibitze
@sellibitze: Well, it's the original static type of the initial throw expression (which is what I was thinking of originally), or the dynamic type of the originally thrown object which is a more accurate statement. I'll edit.
Charles Bailey