Мне было интересно, может быть, было бы лучше использовать множественное наследование для наследования от каждого из производных классов std :: exception
Обратите внимание, что это проблема, из-за того, что исключения в стандартной библиотеке происходят не виртуально друг от друга. Если вы введете множественное наследование, вы получите иерархию исключений страшного бриллианта без виртуального наследования и не сможете перехватывать производные исключения на std::exception&
, поскольку ваш производный класс исключений содержит два подобъекта std::exception
, что делает std::exception
an " Неоднозначный базовый класс ».
Конкретный пример:
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) {
// ...
}
не будет работать.
Изменить:
Мне кажется, я видел первый пример иерархии классов исключений с участием MI в одной из книг Страуструпа, поэтому я пришел к выводу, что в целом это хорошая идея. То, что исключения из 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
экземпляр. Но тогда это было не нужно, поэтому мне это никогда не приходило в голову.