2015-10-28 8 views
63

Пожалуйста, обратите внимание на следующее бросании исключения и ловли:Улавливает ли исключение ссылкой ссылкой?

void some_function() { 
    throw std::exception("some error message"); 
} 

int main(int argc, char **argv) { 
    try { 
     some_function(); 
    } catch (const std::exception& e) { 
     std::cerr << e.what() << std::endl; 
     exit(1); 
    } 
    return 0; 
} 

Безопасно, чтобы поймать брошенное исключение по ссылке?

Меня беспокоит то, что исключение e на самом деле , размещенное в стеке of some_function(). Но some_function() только что вернулся, в результате чего e будет уничтожен. Итак, на самом деле теперь e указывает на разрушенный объект.

Является ли мое беспокойство правильным?

Каков правильный способ передачи исключения без копирования его по значению? Должен ли я бросать new std::exception(), чтобы он был помещен в динамическую память?

+7

Обратите внимание, что конструктор строк для std :: exception не является стандартным, это расширение MS (соответствующее?). http://stackoverflow.com/questions/5157206/why-does-stdexception-have-extra-constructors-in-vc – Sigismondo

+0

Итак, как вы можете назначить сообщение исключению, поэтому оно печатается с помощью 'what()'? – SomethingSomething

+4

@SomethingSomething вы использовали бы производную от 'std :: exception', которая позволяет передавать сообщение конструктору. Например, 'std :: runtime_error'. И соглашение о метании подтипов является именно той причиной, по которой вы должны поймать ссылку (const). – user2079303

ответ

93

это действительно безопасно - и рекомендовал - поймать с помощью const ссылки.

"e фактически помещается на стек some_function()"

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

[except.throw] 15.1/4: Памяти для объекта исключения выделяются в неустановленном , за исключением случаев, указанных в п. 3.7.4.1. Исключение объект уничтожается после того, как последний оставшийся активный обработчик для исключений выйдет любыми способами, кроме ретрансляции, или уничтожается последний объект типа std :: exception_ptr (18.8.5), который ссылается на объект исключения, в зависимости от того, что позже.

Если локальный переменная задана в throw, он скопировал там к в случае необходимости (оптимизатор может быть в состоянии непосредственно создать его в этой другой памяти). Вот почему ...

15,1/5 Когда брошено объект является объектом класса, конструктор, выбранным для копирования инициализации и деструктора должен быть доступен, даже если операция копирования/перемещения опущена (12,8).


Если не нажата, она мощь поможет представить реализацию туманно, как это:

// implementation support variable... 
thread__local alignas(alignof(std::max_align_t)) 
    char __exception_object[EXCEPTION_OBJECT_BUFFER_SIZE]; 

void some_function() { 
    // throw std::exception("some error message"); 

    // IMPLEMENTATION PSEUDO-CODE: 
    auto&& thrown = std::exception("some error message"); 
    // copy-initialise __exception_object... 
    new (&__exception_object) decltype(thrown){ thrown }; 
    throw __type_of(thrown); 
    // as stack unwinds, _type_of value in register or another 
    // thread_local var... 
} 

int main(int argc, char **argv) 
{ 
    try { 
     some_function(); 
    } // IMPLEMENTATION: 
     // if thrown __type_of for std::exception or derived... 
     catch (const std::exception& e) { 
     // IMPLEMENTATION: 
     // e references *(std::exception*)(&__exception_object[0]); 
     ... 
    } 
} 
+0

Я думаю, что 15.1.3 так же актуально, если не больше. Особенно «* Бросок копии исключений - инициализирует (8.5, 12.8) временный объект [...] *», поэтому 'e' никогда не указывает на брошенный объект, а на копию - объект исключения, который использовался для инициализации аргумент 'catch'. – luk32

+1

@ luk32: Я не убежден: важно то, что время жизни объекта исключений превосходит стеки, отматывающие назад к соответствующим операторам (catch) 'catch' - какое отношение имеет к этому отношение 15.1.3? Тем не менее, не стесняйтесь повышать ответ Сигидзодо, если вы этого еще не сделали. Ваш собственный перефразирование/выводы из 15.1.3 является ошибочным IMHO - создание объекта исключений может быть устранено, поэтому нет необходимости в двух разных объектах, о которых такие утверждения, как «никогда не указывает на брошенный объект, а на копию», могут быть точно выполнены. –

+1

Умм .. Хорошо, позвольте мне немного отступить, это так же важно. Я хочу сказать, что все, что бросается, может быть мертвым в пределах ареала. Семантически функция прекратилась. Если компилятор решает, что хочет выполнить elision, это нормально. Дело в том, что * объект исключения * не является тем, что вы передаете в оператор 'throw', но созданным из него * временным *. Их жизнь не привязана друг к другу. Я обновил оба. – luk32

18

Поймать константой ссылку точно как исключения должны быть пойманы. Объект исключения не обязательно должен находиться в стеке. Компилятор отвечает за соответствующую магию, чтобы сделать эту работу.

С другой стороны, ваш пример не может быть скомпилирован, так как std::exception может быть сконфигурирован по умолчанию или создан для копирования. В этом случае метод what() вернет указатель на пустую строку (c-style), что не особенно полезно.

Предлагает Вам бросить std::runtime_error или std::logic_error в зависимости от обстоятельств, или класс, производный от него:

  • logic_error, когда вызывающий абонент запросил что-то вне конструктивных параметров вашей службы.
  • runtime_error, когда вызывающий абонент запросил что-то разумное, но внешние факторы не позволили вам выполнить запрос.

http://en.cppreference.com/w/cpp/error/exception

22

Вы есть поймать по ссылке, иначе вы не могли бы получить правильный динамический тип объекта. Что касается его жизни, стандартных гарантии, в [except.throw],

Объект исключения уничтожен после того, как либо последний оставшийся активного обработчика для выходов исключения иного способа, кроме Повторного выбрасывания, или последнего объекта типа станда средства :: exception_ptr (18.8.5), который ссылается на объект исключения разрушается, в зависимости от того позже

+4

Ну, вы не * должны *, законно это не делать. Однако, если вы этого не сделаете, вы уже нацеливаете пистолет на свое собственное колено. Вы вводите возможную промежуточную копию и, что более важно, нарезку объектов, если вы поймаете по значению. – luk32

8

От except.throw:

Вызывая исключения копирования инициализирует (8.5, 12.8) временный объект, , называемый объектом исключения. Временной является значением lvalue и используется для инициализирует переменную, объявленную в соответствующем обработчике (15.3). Если тип объекта исключения был бы неполным или указателем неполным типом, отличным от (возможно, cv-qualified) void , программа плохо сформирована.

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