Избегайте нарезки типов исключений (C++)

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

5 ответов

Решение

Я хотел бы избежать разработки иерархии исключений, отличной от вашей библиотеки. Использовать std::exception иерархии, насколько это возможно, и всегда выводите свои исключения из чего-то в этой иерархии. Возможно, вы захотите прочитать раздел об исключениях в FAQ по C++ от Marshall Cline - в частности, прочтите 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++ будут иметь те же знания - но добавлю примечание к любой документации, просто чтобы быть уверенным.

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

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

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

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

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

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

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

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

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

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

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

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

Другие вопросы по тегам