Ответ 1
Мне было интересно, возможно, было бы лучше использовать множественное наследование для наследования от каждого из производных классов std:: exception
Обратите внимание, что это проблема из-за того, что исключения в стандартной библиотеке выводятся практически не из-за друг друга. Если вы вводите многократное наследование, вы получаете иерархию исключений страшного алмаза без виртуального наследования и не сможете уловить производные исключения с помощью std::exception&
, так как ваш производный класс исключений несет два подобъекта std::exception
, делая std::exception
a "неоднозначный базовый класс".
Конкретный пример:
class my_exception : virtual public std::exception {
// ...
};
class my_runtime_error : virtual public my_exception
, virtual public std::runtime_error {
// ...
};
Теперь my_runtime_error
выводится (косвенно) из std::exception
дважды, один раз через std::run_time_error
и один раз через my_exception
. Так как первая не получается из std::exception
практически, это
try {
throw my_runtime_error(/*...*/);
} catch( const std::exception& x) {
// ...
}
не будет работать.
Edit:
Я думаю, что я видел первый пример иерархии классов исключений с участием MI в одной из книг Stroustrup, поэтому я пришел к выводу, что в целом это хорошая идея. То, что исключения std lib не происходят практически друг от друга, я считаю неудачей.
Когда я в последний раз разрабатывал иерархию исключений, я очень сильно использовал MI, но не получал из классов исключений std lib. В этой иерархии существовали абстрактные классы исключений, которые вы определили, чтобы ваши пользователи могли их поймать и соответствующие классы реализации, полученные из этих абстрактных классов и из базового класса реализации, которые вы на самом деле бросали. Чтобы это стало проще, я определил некоторые шаблоны, которые будут выполнять всю тяжелую работу:
// something.h
class some_class {
private:
DEFINE_TAG(my_error1); // these basically define empty structs that are needed to
DEFINE_TAG(my_error2); // distinguish otherwise identical instances of the exception
DEFINE_TAG(my_error3); // templates from each other (see below)
public:
typedef exc_interface<my_error1> exc_my_error1;
typedef exc_interface<my_error2> exc_my_error2;
typedef exc_interface<my_error3,my_error2> // derives from the latter
exc_my_error3;
some_class(int i);
// ...
};
//something.cpp
namespace {
typedef exc_impl<exc_my_error1> exc_impl_my_error1;
typedef exc_impl<exc_my_error2> exc_impl_my_error2;
typedef exc_impl<exc_my_error3> exc_impl_my_error3;
typedef exc_impl<exc_my_error1,exc_my_error2> // implements both
exc_impl_my_error12;
}
some_class::some_class(int i)
{
if(i < 0)
throw exc_impl_my_error3( EXC_INFO // passes '__FILE__', '__LINE__' etc.
, /* ... */ // more info on error
);
}
Оглядываясь назад, я думаю, что я мог бы сделать этот шаблон exc_impl
из std::exception
(или любого другого класса в иерархии исключений std lib, передан как необязательный параметр шаблона), поскольку он никогда не происходит из любой другой экземпляр exc_impl
. Но тогда это не было необходимо, поэтому мне это никогда не приходило в голову.