C++: нарезка объектов и исключения

В одном из интервью меня спросили, почему перехват исключений по значению может быть проблемой, и я ответил, что это может вызвать нарезку объектов. И это то, что я нахожу в Интернете, например, здесь: https://www.viva64.com/en/w/v746/

Но сейчас я пытаюсь экспериментировать и не могу найти пример нарезки при ловле по значению. Обычный сценарий нарезки (без исключений) таков:

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

В последней строке вызывается оператор присваивания Base, который копирует только базовую часть производного объекта. Таким образом, основанная часть b1 копируется из d2, а производная часть b1 остается из d2. ПЛОХОЙ.

Но как это может произойти при отлове исключений по значению?

Я попробовал этот код (с обоими компиляторами: g++ и Sun CC):

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();
    }    
}

Выход был:

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

Поэтому я выбрасываю исключение Derived, перехватываю его Base BY VALUE и перебрасываю, затем перехватываю Derived BY VALUE, и все работает нормально, никаких срезов. Как так?

А может кто-нибудь привести пример нарезки при ловле по значению?

2 ответа

Даже если catch(Base) сделать нарезку Derived объект, реthrow использует оригинальный объект исключения вместо нарезанной копии.

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

Отбрасывает текущее обработанное исключение. Отменяет выполнение текущего блока catch и передает управление следующему соответствующему обработчику исключений (но не другому предложению catch после того же блока try: его составной оператор считается "завершенным"), повторно используя существующий объект исключения: новые объекты не создаются. Эта форма разрешена только в том случае, если в настоящее время обрабатывается исключение (она вызывает std::terminate, если используется иначе). Предложение catch, связанное с функцией-try-block, должно завершиться с помощью rethrowing, если оно используется в конструкторе.


Обратите внимание, что если вы замените throw; от throw x;, Base экземпляр будет брошен и не будет пойман, в результате чего std::abort() быть названным. Действительно, нарезанный Derived не может быть не нарезанным (именно поэтому мы обычно не любим кусочки, если они не кусочки пиццы), чтобы быть пойманным catch (Derived),

В заключение я бы остановился на "Бросок по значению, улов по (const) ссылке)". В этом конкретном примере вы хорошо ловите по разрезанному значению, но это не совсем так. В ответе Сержа Баллеста приведен пример улова по стоимости, приводящего к неприятностям. Быстрый поиск в вашей любимой поисковой системе может помочь вам найти другие случаи, когда поиск по значению приводит к проблемам.

Еще одна проблема отлова исключения по значению состоит в том, что для него требуется полная копия исключения. Если вы близки к условию Stackru (или уже обрабатываете его), вы можете оказаться в ситуации, когда копирование невозможно, и предложение catch не может быть выполнено.

Другие вопросы по тегам