Избегайте нарезки типов исключений (C ++) - PullRequest
5 голосов
/ 06 декабря 2009

Я проектирую иерархию исключений в C ++ для моей библиотеки. «Иерархия» - это 4 класса, полученных из std :: runtime_error. Я хотел бы избежать проблемы нарезки для классов исключений, чтобы защитить конструкторы копирования. Но, очевидно, gcc требует вызывать конструктор копирования при создании их экземпляров, поэтому жалуется на конструкторы защищенных копий. Visual C ++ 8.0 прекрасно компилирует тот же код. Есть ли какой-нибудь переносимый способ разрядки проблемы срезов для классов исключений? Говорит ли стандарт что-либо о том, может ли реализация / должна требовать конструктора копирования класса, который должен быть брошен?

Ответы [ 5 ]

15 голосов
/ 06 декабря 2009

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

Решение вашей проблемы - всегда ловить по ссылке:

try {
    // some code...
    throw MyException("lp0 is on fire!");
} catch (MyException const &ex) {
    // handle exception
}

(const -ness необязательна, но я всегда вставляю ее, потому что редко требуется модифицировать объект исключения.)

9 голосов
/ 06 декабря 2009

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

6 голосов
/ 06 декабря 2009

Я хотел бы избежать разработки иерархии исключений, отдельной для вашей библиотеки. Максимально используйте иерархию std::exception, и всегда выводит ваши исключения из чего-то внутри этой иерархии. Возможно, вы захотите прочитать часть исключений в FAQ C ++ Маршалла Клайна - прочитать FAQ 17,6 , 17,9 , 17,10 и В частности, 17.12 .

Что касается , "заставляющего пользователей ловить по ссылке" , я не знаю, как это сделать. Единственный способ, которым я придумал через час или около того игры (это воскресенье днем), основан на полиморфном броске :

class foo_exception {
public:
    explicit foo_exception(std::string msg_): m_msg(msg_) {}
    virtual ~foo_exception() {}
    virtual void raise() { throw *this; }
    virtual std::string const& msg() const { return m_msg; }
protected:
    foo_exception(foo_exception const& other): m_msg(other.m_msg) {}
private:
    std::string m_msg;
};

class bar_exception: public foo_exception {
public:
    explicit bar_exception(std::string msg_):
        foo_exception(msg_), m_error_number(errno) {}
    virtual void raise() { throw *this; }
    int error_number() const { return m_error_number; }
protected:
    bar_exception(bar_exception const& other):
        foo_exception(other), m_error_number(other.m_error_number) {}
private:
    int m_error_number;
};

Идея состоит в том, чтобы сделать конструктор копирования защищенным и заставить пользователей вызывать Class(args).raise() вместо throw Class(args). Это позволяет создавать исключение с полиморфной привязкой, которое ваши пользователи могут отлавливать только по ссылке. Любая попытка поймать по значению должна приветствоваться хорошим предупреждением компилятора. Что-то вроде:

foo.cpp: 59: ошибка: «bar_exception :: bar_exception (const bar_exception &)» защищена

foo.cpp: 103: ошибка: в этом контексте

Конечно, все это имеет свою цену, поскольку вы больше не можете явно использовать throw, иначе вас встретит аналогичное предупреждение компилятора:

foo.cpp: в функции void h () ’:

foo.cpp: 31: ошибка: «foo_exception :: foo_exception (const foo_exception &)» защищена

foo.cpp: 93: ошибка: в этом контексте

foo.cpp: 31: ошибка: «foo_exception :: foo_exception (const foo_exception &)» защищена

foo.cpp: 93: ошибка: в этом контексте

В целом, я бы полагался на стандарты кодирования и документацию, в которой указано, что вы всегда должны ловить ссылки. Убедитесь, что ваша библиотека перехватывает исключения, которые она обрабатывает по ссылке, и генерирует свежие объекты (например, throw Class(constructorArgs) или throw;). Я ожидал бы, что другие программисты C ++ будут иметь те же знания - но для большей уверенности добавлю примечание к любой документации.

0 голосов
/ 27 ноября 2014

Два найденных мною портативных способа остановить неправильный перехват исключений клиентами по значению:

  1. Бросать исключения изнутри Виртуальное повышение методов классов исключений и защищать конструкторы копирования. (Спасибо Д.Шоули)
  2. Бросать производные исключения из библиотеки и публиковать базовые классы исключений, чтобы клиенты могли их перехватить. Базовые классы могут иметь защищенные конструкторы копирования, что позволяет только ловить их. (упомянуто здесь для простого вопроса)

Стандарт C ++ заявляет, что конструктор копирования должен быть доступен в момент выдачи. Visual C ++ 8.0 в моей конфигурации нарушил эту часть стандарта, не предписывая наличие конструктора копирования. В разделе 15.1.3:

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

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

Этот ответ был добавлен ОП в вопрос, я удалил его из вопроса и опубликовал в виде отдельного ответа.

0 голосов
/ 07 декабря 2009

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

...