3

In an interview I was asked why catching exceptions by value can be a problem and I answered that this can cause object slicing. And this is what I find in the Internet, for example here: https://www.viva64.com/en/w/v746/

But now I am trying to experiment and I cannot find an example of slicing when catching by value. Usual scenario of slicing (not with exceptions) is this:

Derived d1;
Derived d2;
Base& b1 = d1;
Base& b2 = d2;
b1 = b2;

In the last line assignment operator of Base is called, which copies only Base part of Derived object. So Based part of b1 is copied from d2, whilst Derived part of b1 remains from d2. BAD.

But how can this happen when catching exceptions by value?

I tried this code (with both: g++ and Sun CC compilers):

struct Base
{
    virtual void print() const
    {
        cout << "{ Base: " << m << " }" << endl;
    }

    Base(int _m = 0) : m(_m) {}

    int m;
};

struct Derived : Base
{
    Derived(int _m = 0, int _n = 0) : Base(_m), n(_n) {}

    void print() const
    {
        cout << "{ Base: " << m << ", Derived: " << n << " }" << endl;
    }

    int n;
};

int main()
{
    try
    {
        try
        {
            throw Derived(3, 300);
        }
        catch(Base x)
        {
            cout << "Inner catch: ";
            x.print();
            throw;
        }
    }
    catch(Derived y)
    {
        cout << "Outer catch: ";
        y.print();
    }    
}

The output was:

Inner catch: { Base: 3 }
Outer catch: { Base: 3, Derived: 300 }

So I throw Derived exception, catch its Base BY VALUE and rethrow, then catch Derived BY VALUE and all works fine, no any slicing. How is that?

And can somebody provide an example of slicing when catching BY VALUE?

AndreyKarpov
  • 1,083
  • 6
  • 17
Andrey Rubliov
  • 1,359
  • 2
  • 17
  • 24

2 Answers2

5

Even though catch(Base) do slice the thrown Derived object, the rethrow uses the original exception object instead of the sliced copy.

From http://en.cppreference.com/w/cpp/language/throw:

Rethrows the currently handled exception. Abandons the execution of the current catch block and passes control to the next matching exception handler (but not to another catch clause after the same try block: its compound-statement is considered to have been 'exited'), reusing the existing exception object: no new objects are made. This form is only allowed when an exception is presently being handled (it calls std::terminate if used otherwise). The catch clause associated with a function-try-block must exit via rethrowing if used on a constructor.


Note that if you replace throw; by throw x;, a Base instance will be thrown and won't be catch, resulting to std::abort() to be called. Indeed, the sliced Derived cannot be unsliced (this is why we generally don't like slices, unless they are pizza slices) to be catched by catch (Derived).

As a conclusion, I'd stick with "Throw by value, catch by (const) reference)". In this specific example you're good with catching by a sliced value, but is is not the case in general. Serge Ballesta's answer provide an example of a catch by value leading to troubles. A quick search on your favorite search engine can help you find other cases where catching by value is looking for troubles.

YSC
  • 38,212
  • 9
  • 96
  • 149
  • OK, so recommendation for throwing and catching exceptions: 'Throw by value, catch by reference' can be in fact replaced by 'Throw by value, catch by whatever you want, re-throw with pure throw without arguments'. (In case you want to modify your exception object, catch by reference). – Andrey Rubliov May 24 '18 at 14:17
  • @AndreyRubliov If this answer suits you, you can accept it by checking the mark under its score. If it doesn't suit you, you can ask question/suggest improvements. – YSC May 25 '18 at 15:54
1

One other problem of catching Exception by value, it that it requires a full copy of the exception. If you are close to a StackOverflow condition (or already processing one), you could be in a use case where the copy would not be possible, and the catch clause could not be executed.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • Right. Another problem with catching by value is if the copy constructor of the exception class can throw exceptions (and it can do that in case of low memory). Then std::terminate() will be called. – Andrey Rubliov May 24 '18 at 14:19