Поймать по ссылке, когда переменная исключения не определена

При перехвате исключения стандартное руководство состоит в том, чтобы выбрасывать по значению, перехватывать по ссылке. Насколько я понимаю, это по двум причинам:

  1. Если исключение было вызвано из-за нехватки памяти, мы не будем вызывать конструктор копирования, который потенциально может завершить программу.
  2. Если исключение является частью иерархии наследования, у нас потенциально может быть нарезка объекта на исключение.

Если у нас есть сценарий, в котором мы не определяем имя исключения в блоке catch, остаются ли в силе эти проблемы (действительно 1., поскольку нарезка не будет проблемой, если у нас нет имени для переменной)?

Например:

catch(my_exception)
{ ... }

or

catch(my_exception &)
{ ... }

Есть ли еще возможность завершения программы, если в этом случае исключение поймано по значению? Я чувствую, что это технически все еще возможно.

Примечание. Я спрашиваю об этом, потому что мне пришлось просмотреть чей-то код, который в этом случае поставил улов по значению. Как показано в вопросе, я не совсем уверен в технических последствиях любого выбора, но я думаю, что с точки зрения согласованности в этом случае лучше ловить по ссылке (в любом случае нет недостатка в ловле по ссылке) .


person Christopher Howlin    schedule 13.09.2011    source источник
comment
Несмотря на то, что перехват my_exception& является стандартной практикой, я обнаружил, что в большинстве случаев исключение не изменяется, и поэтому было бы лучше перехватить my_exception const&. Обратите внимание, что what равно const.   -  person Matthieu M.    schedule 13.09.2011
comment
Этот вопрос вдохновил на этот вопрос об исключении копии в таком случае: встречаются в операторах catch"> stackoverflow.com/questions/7401521/   -  person Potatoswatter    schedule 13.09.2011


Ответы (3)


Стандарт не требует специальной оптимизации в случае безымянного объекта исключения. Напротив, тогда требуется эффект, как если бы временный объект был инициализирован копированием. Это копирование может привести к динамическому выделению памяти.

N3290 §15.3/16:
Если в объявлении-исключения указано имя, оно объявляет переменную, которая инициализируется копированием (8.5) из объекта-исключения. Если объявление-объявления-исключения указывает тип объекта, но не указывает имя, временное (12.2) инициализируется копированием (8.5) из объекта-исключения. Время жизни переменной или временного объекта заканчивается, когда обработчик завершает работу после уничтожения любых автоматических объектов, инициализированных в обработчике.

В приведенном выше абзаце не упоминается перехват по ссылке, поэтому можно разумно заключить, что он применяется независимо от того, перехватывается ли объект исключения по ссылке; что копия строится в любом случае.

Однако этому противоречит следующий абзац:

N3290 §15.3/17:
Когда обработчик объявляет непостоянный объект, любые изменения этого объекта не повлияют на временный объект, который был инициализирован выполнением выражения throw-expression. . Когда обработчик объявляет ссылку на непостоянный объект, любые изменения в объекте, на который делается ссылка, являются изменениями во временном объекте, инициализированном при выполнении throw-expression, и будут действовать, если этот объект будет создан повторно.

Таким образом, объявленный тип T&T не-const) — это единственный случай, когда С++ 11 требует прямой ссылки на брошенный объект вместо копирования. То же самое и в C++03, за исключением того, что в C++03 есть некоторые дополнительные формулировки об оптимизации «как если бы». Таким образом, для формального предпочтение должно быть для

    catch( T& name )

и

    catch( T& )

Однако я всегда ловил такие исключения, как catch( T const& ). С практической точки зрения можно предположить, что компилятор оптимизирует это до прямой ссылки на выбрасываемый объект, хотя можно придумать случаи, когда наблюдаемый программный эффект будет тогда нестандартным. Например <evil grin>

#include <stdio.h>
#include <stdexcept>

struct Error
    : std::runtime_error
{
public:
    static Error* pThrown;

    char const* pMessage_;
    Error()
        : std::runtime_error( "Base class message" )
        , pMessage_( "Original message." )
    {
        printf( "Default-construction of Error object.\n" );
        pThrown = this;
    }

    Error( Error const& other )
        : std::runtime_error( other )
        , pMessage_( other.pMessage_ )
    {
        printf( "Copy-construction of Error obejct.\n" );
    }

    char const* what() const throw() { return pMessage_; }
};

Error*  Error::pThrown  = 0;

int main()
{
    printf( "Testing non-const ref:\n" );
    try
    {
        throw Error();
    }
    catch( Error& x )
    {
        Error::pThrown->pMessage_ = "Modified message.";
        printf( "%s\n", x.what() );
    }

    printf( "\n" );
    printf( "Testing const ref:\n" );
    try
    {
        throw Error();
    }
    catch( Error const& x )
    {
        Error::pThrown->pMessage_ = "Modified message";
        printf( "%s\n", x.what() );
    }
}

Как с MinGW g++ 4.4.1, так и с Visual C++ 10.0 вывод

Testing non-const ref:
Default-construction of Error object.
Modified message.

Testing const ref:
Default-construction of Error object.
Modified message

Педантичный формалист мог бы сказать, что оба компилятора не соответствуют требованиям и не могут создать копию для случая Error const&. Чисто практический практик может сказать, что эй, что еще вы ожидали? И я говорю, что формулировка в стандарте здесь очень далека от совершенства, и что, во всяком случае, следует ожидать уточнения, чтобы явно разрешить вывод выше, так что также отлов по ссылке на const и безопасен, и максимально эффективен.

Подводя итоги вопрос ОП:

  • Перехват по ссылке не вызовет конструктор копирования, который потенциально может завершить программу.

  • Стандарт гарантирует это только для ссылки на не-const.

  • На практике, как показано, это также гарантируется для ссылки на const, даже если это повлияет на результаты программы.

Ура и чт.,

person Cheers and hth. - Alf    schedule 13.09.2011

Я бы предпочел ловить по ссылке. Компилятор может отбросить исключение как оптимизацию и не копировать, но это только возможность. Ловля по ссылке дает вам уверенность.

person R. Martinho Fernandes    schedule 13.09.2011
comment
Вопрос: может ли компилятор оптимизировать копию, даже если конструктор копирования исключения имеет побочные эффекты? - person R. Martinho Fernandes; 13.09.2011

С вашим исключением могут быть невидимые данные, например. vtable.

Виртуальная таблица — это невидимая структура данных, содержащая информацию о том, где можно найти определенные полиморфные (то есть virtual) функции-члены. Эта таблица в общем случае требует немного памяти, которая содержится в самом объекте. Это может быть размер указателя на какую-то внешнюю таблицу или даже всю таблицу. Как всегда, это зависит.

person Sebastian Mach    schedule 13.09.2011
comment
Не могли бы вы уточнить, что это может означать в данном контексте? - person Christopher Howlin; 13.09.2011