Исключение нарезки - это связано с созданным конструктором копирования?
Я только что исправил очень тонкую ошибку в нашем коде, вызванную нарезкой исключения, и теперь я хочу убедиться, что я точно понимаю, что происходит.
Вот наш базовый класс исключений, производный класс и соответствующие функции:
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;
}
}
Конечно, ошибка заключалась в том, что externalcall заканчивал тем, что выбрасывал исключение вместо производного. Моя ошибка возникла из-за того, что в стеке попыток перехватить Derived произошел сбой.
Теперь я просто хочу убедиться, что я понимаю - я считаю, что в строке 'throw e' создается новый объект Exception с использованием конструктора копирования по умолчанию. Это то, что действительно происходит?
Если да, могу ли я заблокировать конструкторы копирования для объектов, которые будут выброшены? Я действительно предпочел бы, чтобы это больше не повторилось, и у нашего кода нет причин копировать объекты Exception (о которых я знаю).
Пожалуйста, не комментируйте тот факт, что у нас есть собственная иерархия исключений. Это немного старый дизайн, над которым я работаю над исправлением (я делаю хорошие успехи. Я избавился от класса домашних строк и многих контейнеров, выращенных дома).
ОБНОВЛЕНИЕ: чтобы было ясно, я исправил ошибку (изменив "throw e" на "throw") еще до того, как задал вопрос. Я просто искал подтверждение того, что происходит.
4 ответа
Когда вы бросаете объект, вы фактически выбрасываете копию объекта, а не оригинал. Подумайте об этом - исходный объект находится в стеке, но стек разматывается и становится недействительным.
Я считаю, что это является частью стандарта, но у меня нет копии для ссылки.
Тип исключения, создаваемого в блоке catch, является базовым типом catch, а не типом брошенного объекта. Обойти эту проблему можно throw;
скорее, чем throw e;
который бросит оригинальное пойманное исключение.
Беглый гугл подсказывает, что да, вы выкидываете конструктор копирования обязательный и должен быть публичным. (Что имеет смысл, так как вы инициализируете копию e
и бросать это.)
В любом случае, просто используйте throw
без указания объекта исключения, чтобы отбросить то, что было поймано в catch
, Разве это не решит проблему?
catch(Exception& e)
{
printf("Exception seen here! %s %d\n", __FILE__, __LINE__);
throw;
}
Да.
throw e;
выдает исключение статического типа e
независимо от того, что e
на самом деле В этом случае Derived
исключение копируется в Exception
используя конструктор копирования.
В этом случае вы можете просто
throw;
чтобы получить Derived
Исключение всплывают правильно.
Если вы интересуетесь полиморфным броском в некоторых других случаях, обратитесь к всегда так полезному C++ FAQ Lite.
C++ не перестает меня удивлять. Я бы потерял много денег, если бы это было пари на поведение!
Объект исключения сначала копируется во временный, и вы должны были использовать throw
, Чтобы процитировать стандарт 15.1/3:
Выражение throw инициализирует временный объект, называемый объектом исключения, тип которого определяется путем удаления любых cv-квалификаторов верхнего уровня из статического типа операнда throw и корректировки типа из "массива T" или " функция, возвращающая T "to" указатель на T "или" указатель на функцию, возвращающую T ", соответственно.
Я думаю, что это приводит к очень полезному стандартному правилу кодирования:
Базовые классы иерархии исключений должны иметь чистый виртуальный деструктор.
или же
Конструктор копирования для базового класса в иерархии исключений должен быть защищен.
Либо достигается цель, которую компилятор предупредит, когда вы попытаетесь "выбросить e", поскольку в первом случае вы не можете создать экземпляр абстрактного класса, а во втором - потому что вы не можете вызвать конструктор копирования.