简体   繁体   中英

C++: object slicing and exceptions

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?

Even though catch(Base) do slice the thrown Derived object, the re throw 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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM