2009-12-06 2 views
5

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

+0

Практически везде, где может использоваться конструктор копирования, стандарт C++ позволяет использовать его. Но если вы хотите, чтобы ваш код был переносимым, исключения должны иметь публично доступный конструктор копирования. – 2009-12-06 16:57:17

+0

Кроме того, я не знаю, какую версию VC++ вы используете, но вы, старый VC++ 6.0, действительно нуждались в конструкторе копирования - я только что протестировал его. – 2009-12-06 17:01:20

ответ

6

Я бы избегал проектирования иерархии исключений, отличной от вашей библиотеки. Используйте как можно больше иерархию std::exception, и всегда выведите свои исключения из чего-то внутри этой иерархии. Возможно, вы захотите прочитать exceptions portion of Marshall Cline's C++ FAQ - см. FAQ 17.6, 17.9, 17.10 и 17.12 в частности.

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

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: error: ‘bar_exception::bar_exception(const bar_exception&)’ is protected

foo.cpp:103: error: within this context

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

foo.cpp: In function ‘void h()’:

foo.cpp:31: error: ‘foo_exception::foo_exception(const foo_exception&)’ is protected

foo.cpp:93: error: within this context

foo.cpp:31: error: ‘foo_exception::foo_exception(const foo_exception&)’ is protected

foo.cpp:93: error: within this context

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

+0

Спасибо за ваше время! Все броски в библиотеке в любом случае выполняются с помощью макроса, чтобы включить кодирование имени файла и номера строки в исключение, поэтому я могу просто изменить определение макроса для вызова рейза. – shojtsy

15

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

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

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

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

+3

Кроме того: всегда создавайте вновь созданные исключения (или, по крайней мере, исключения, когда вы знаете, как они были созданы). Не бросайте с помощью разыменованного указателя или ссылки, которая может относиться к производному классу. –

+0

Хорошая точка. Я обновил этот пример, чтобы привести пример «хорошего» броска. – Thomas

+0

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

9

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

+2

+1. Я с Нилом здесь. Если вы считаете, что у вас есть исключение, которое встречается чаще, чем другие, и поэтому ему нужен собственный тип исключения (у вас, вероятно, есть ошибка, а не исключение). В противном случае 1 исключение на функциональный блок (как вы определяете функциональный блок, остается неопределенным, но больше, а не меньше). –

-3

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

0

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

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

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

A throw-expression initializes a temporary object, the type of which is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T”, respectively.

If the use of the temporary object can be eliminated without changing the meaning of the program except for the execution of constructors and destructors associated with the use of the temporary object (12.2), then the exception in the handler can be initialized directly with the argument of the throw expression. When the thrown object is a class object, and the copy constructor used to initialize the temporary copy is not accessible, the program is ill-formed (even when the temporary object could otherwise be eliminated)

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

+0

Хотя для автора библиотеки разумно затруднить работу с библиотекой, но также важно не подрывать стандарт. В стандарте говорится, что объекты исключений должны быть скопированы (15.1.5), и он явно позволяет ловить по значению. Я бы с этим не согласился. –

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