2009-07-07 1 views
8

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

Вот наш базовый класс исключений, производный класс, и соответствующие функции:

class Exception 
{ 
public: 
    // construction 
    Exception(int code, const char* format="", ...); 
    virtual ~Exception(void); 

    <snip - get/set routines and print function> 

protected: 
private: 
    int mCode;    // thrower sets this 
    char mMessage[Exception::MessageLen]; // thrower says this FIXME: use String 
}; 

class Derived : public Exception { 
public: 
    Derived (const char* throwerSays) : Exception(1, throwerSays) {}; 
}; 

void innercall { 
    <do stuff> 
    throw Derived("Bad things happened!"); 
} 

void outercall { 
    try { 
    innercall(); 
    } 
    catch(Exception& e) 
    { 
    printf("Exception seen here! %s %d\n", __FILE__, __LINE__); 
    throw e; 
    } 
} 

ошибка была, конечно, что outercall заканчивает бросать исключение, а не производный. Моя ошибка возникла из-за того, что в стеке вызовов было больше попыток поймать Derived failing.

Теперь я просто хочу убедиться, что я понимаю - я считаю, что на линии «throw e» создается новый объект Exception с использованием конструктора копии по умолчанию. Это то, что на самом деле происходит?

Если это так, мне разрешено блокировать конструкторы копирования для объектов, которые будут выбрасываться? Я бы предпочел, чтобы это не повторилось, и наш код не имеет причины копировать объекты Exception (что я знаю).

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

ОБНОВЛЕНИЕ: Чтобы быть ясным, я исправил ошибку (изменив «throw e» на «throw»), прежде чем задавать вопрос. Я просто искал подтверждения того, что происходит.

+2

Я знаю, что вы сказали, что не беспокоитесь о других вещах, но просто еще две вещи: сделайте свой класс 'Exception' унаследованным от' std :: exception' и поймайте вещи 'const &'. – GManNickG

+0

В чем преимущество ловить как const? Я всегда поймаю по ссылке, но почему const? (не то, что я могу сделать это с этим классом в любой момент, но в один прекрасный день ...) –

+1

@ Kohne: Преимущество const в этой настройке аналогично тому, как в любой другой настройке - вы четко указываете намерение, t намерены изменить состояние объекта и что компилятор должен убедиться, что вы этого не сделаете - идея const заключается в том, что неизменные объекты легче рассуждать (т.е. отлаживать, тестировать, доказывать корректность кода), поскольку они помогают в письменной форме ссылаться прозрачный код –

ответ

19

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

Я считаю, что это часть стандарта, но у меня нет копии для ссылки.

Тип исключения, создаваемого в блоке catch, является базовым типом catch, а не типом объекта, который был выброшен. Путь вокруг этой проблемы заключается в throw;, а не throw e;, который выкинет исходное исключение.

+0

Это означает, что я не могу выбросить что-то, у которого нет конструктора копий? В этом есть смысл. –

10

A quick google предполагает, что да, вы выбрасываете конструктор копирования и должны быть общедоступными. (Который имеет смысл, так как вы инициализацией копию e и бросали это.)

Во всяком случае, просто использовать throw без указания объекта исключения, чтобы повторно выдать то, что был пойман в catch. Разве это не должно решить проблему?

catch(Exception& e) 
    { 
    printf("Exception seen here! %s %d\n", __FILE__, __LINE__); 
    throw; 
    } 
+0

Прошу прощения, да, я уже исправил эту ошибку, выполнив ретролл должным образом. Я пытался получить подтверждение о конструкторе копирования. –

6

Да.

throw e; 

выбрасывает исключение типа eстатического, независимо от того, какой бы ни e на самом деле. В этом случае исключение Derived копируется в Exception с использованием конструктора копирования.

В этом случае вы можете просто

throw; 

правильно получить пузырь Derived исключения вверх.

Если вы заинтересованы в полиморфном метании в некоторых других случаях, обратитесь к разделу always so useful C++ FAQ Lite.

1

C++ никогда не перестает удивлять меня. Я бы потерял много денег, если бы это была ставка на то, что было поведением!

Объект исключения сначала скопирован на временный, и вы должны были использовать throw. Процитируем стандарт 15.1/3:

Бросок выражение инициализирует временный объект, называемый объект исключения, тип которого определяется путем удаления CV-классификаторов верхнего уровня от статического типа операнда отбрасывания и настройки типа из «массива T» или «функции, возвращающей T», на «указатель на T» или «указатель на функцию возврата T» соответственно.

Я думаю, что это приводит к очень полезным кодирования стандартного правила:

Базовые классы иерархии исключений должны иметь чистый виртуальный деструктор.

или

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

Либо достигает цели, что компилятор предупредит при попытке «кинуть е», так как в первом случае вы не можете создать экземпляр абстрактного класса, а второй, потому что вы не можете вызвать конструктор копирования.

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