2012-06-01 4 views
1

У нас есть пользовательский класс ошибок, который используется всякий раз, когда мы бросаем исключение:C++: Выбрасывание исключения вызывает конструктор копирования?

class AFX_CLASS_EXPORT CCLAError : public CObject 

Он определил следующий конструктор копирования:

CCLAError(const CCLAError& src) { AssignCopy(&src); } // (AssignCopy is a custom function) 

Первоначально она была написана и составлена ​​/ связана с MSVC6 (Visual Studio 2003). Я нахожусь в процессе выполнения необходимых изменений, чтобы получить его для компиляции и ссылку на MSVC8 + (VS 2008+)

Когда msvc8 линкер вызывается, я получаю следующее сообщение об ошибке:

LNK2001: unresolved external symbol "private: __thiscall CObject::CObject(class CObject const &)" ([email protected]@[email protected]@@Z) 

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

Впервые я увидел ошибку при компиляции библиотеки, которая определяет и сначала бросает CCLAError, поэтому я продолжаю, как будто это причина.

Я был в состоянии устранить ошибку, изменив

в

throw new CCLAError(...) 

и

catch(CCLAError& e) 
{ 
    throw e; 
} 

в

catch(CCLAError& e) 
{ 
    throw; 
} 

Однако я не понимаю, почему повторное бросание исключенного пойма вызовет конструктор копирования. Я пропущу что-то совершенно очевидное? Впоследствии почему удаление переменной, содержащей ссылку на исключенное исключение, вызывает вызов конструктора копирования?

+1

Вам не нужно, а также не должно быть, используя 'new' при бросании исходного исключения. Но вам определенно нужно изменить 'throw e', чтобы просто« бросить », чтобы повторно выбросить существующее исключение, не создавая его нового экземпляра. –

ответ

9

Тип брошенного объекта должен быть скопирован, потому что выражение throw может сделать копию его аргумента (копия может быть удалена или на C++ 11 может произойти переход, но конструктор копирования все равно должен быть доступный и вызываемый).

Повторное исключение с использованием throw; не создает никаких копий. Выброс пойманного объекта исключений с использованием throw e; приведет к копированию e. Это не то же самое, что и перестройка исключение.

Ваш «обновленный» код не работает так, как вы ожидаете. catch (CCLAError&) не поймает исключения типа CCLAError*, который является типом исключения, созданного throw new CCLAError(...);. Вам нужно поймать CCLAError*. Однако не делайте этого. Выбросить исключения по значению и выловить по ссылке. Все типы исключений должны быть скопируемыми.

5

However, I do not understand why re-throwing a caught exception would invoke the copy constructor.

Это не делает, но повторно метания выброшенное исключение делается с throw;. Когда вы делаете throw e;, вы запрашиваете копию исключенного пойма.

+0

Делает смысл. Поскольку 'throw e' был действителен в MSVC6, и теперь он не находится в MSVC8 (как показано в действии« throw »), они оба добиваются того же результата? Или старый код утечки памяти? – johnluetke

+1

@johnluetke: Всякий раз, когда вы «бросаете» указатель, тогда его просачивается (попытка _delete_ это хлопотно). И 'throw e' может привести к разрезанию, если вы поймаете значение, поэтому' throw'' есть. –

0

Спецификация языка позволяет компиляторам делать столько копий исходного объекта, сколько захочет. Количество копий не ограничено.

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

2

Несколько точек:

первую очередь не называют throw new foo() использование throw foo

второй, когда Вы пишете:

catch(foo const &e) { 
    throw e; 
} 

Вы на самом деле создать новое исключение, и если, например, исключение что был брошен является подклассом foo, они мне звонили throw e, вы потеряете эту информацию и фактически используете конструктор копирования для генерировать foo с e - независимо было.

Теперь, когда вы звоните

catch(foo const &e) { 
    throw; 
} 

Вы не создаете новое исключение, а скорее распространяется то же исключение.

Итак: никогда использование throw e; распространяться исключение вперед, используйте throw;

+0

На мой комментарий к ответу K-ballo старый код (который говорит 'throw e'), а затем просачивается? – johnluetke

1

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

Когда вы бросаете объект с new, вы не бросаете фактический объект, но вы бросаете указатель на объект. Это означает, что ваш блок catch должен также уловить указатель, а не ссылку. Не забудьте указать delete указатель или у вас будет утечка памяти!

Когда блок catch использует throw; вместо throw e;, он может повторно использовать копию, которую он сделал ранее, и нет необходимости делать другую копию.

0

Похоже, вы неправильно поняли, что такое повторный бросок. Когда вы это сделаете -

catch(CCLAError& e) 
{ 
    throw e; 
} 

- вы НЕ ПЕРЕРЫВЫ. Вместо этого, как вы заметили, вы действительно создаете копию нового исключения. Вместо этого (опять же, как вы нашли для себя), это то, что технически правильный способ повторного броска:

catch(CCLAError& e) 
{ 
    throw; 
} 

Читайте главу 14 в TC++ PL Страуструпа (14.3.1 занимается Повторное выбрасывание).

Кроме того, вы не должны делать -

throw new CCLAError(...) 

Вместо этого сделать -

- как вы делали раньше, получают только по ФИКС ссылки (вы не можете держать ссылка на временную).

catch(const CCLAError &e) 
+0

Что касается вашего последнего предложения: временных файлов нет: 'CCLAError & e' связывается с объектом исключения. Копирование не требуется. Где, по вашему мнению, существует временное? –

+0

'throw CCLAError (...)' вызывает ошибку компоновщика LNK2001, описанную в OP. – johnluetke

Смежные вопросы