为什么这样做:
#include <iostream>
struct base_exc : std::runtime_error
{
base_exc(const std::string& s): std::runtime_error(("base_exc: " + s).c_str()){}
};
struct derived_exc1 : base_exc
{
derived_exc1(const std::string& s): base_exc(("derived_exc1: " + s).c_str()){}
};
struct derived_exc2 : base_exc
{
derived_exc2(const std::string& s): base_exc(("derived_exc2: " + s).c_str()){}
};
template <typename T1, typename T2>
struct binary_exc: T1, T2
{
binary_exc(const std::string& s): T1(s), T2(s){}
};
int main()
{
try{
throw binary_exc<derived_exc2, derived_exc1>("something occured");
}
catch(base_exc const& e)
{
std::cout << e.what() << std::endl;
}
}
输出:
$ g++ -std=c++11 main.cpp && ./main
terminate called after throwing an instance of 'binary_exc<derived_exc2, derived_exc1>'
Aborted (core dumped)
代替:
$ g++ -std=c++11 main.cpp && ./main
base_exc: something occured
我要达到的目标:我想为代码中的某些异常提供两个“正交”分类标准,例如,一个基于代码位置的library1_exc
,library2_exc
,...)和一个基于错误类别(myobject1isoutofbounds_exc
,myobject2isbroken_exc
,..)的文件。
可以使用throw binary_exc<library2_exc, myobject1isoutofbounds_exc>(msg)
之类的方法引发这些异议,而我可以使用以下任何一种方法来捕获它们:
catch(library2_exc const& e)
catch(myobject1isoutofbounds_exc const& e)
catch(base_exc const& e)
将我的代码放在前两个代码的上方-捕获派生类-可以正常工作,但最后一个则不能。为什么?这里有反模式吗?
请注意:
binary_exc
使用虚拟继承会产生相同的结果。 (编辑:我写票时的意思是我尝试过struct binary_exc: virtual T1, virtual T2
)答案 0 :(得分:4)
您链接的增强文档确实是您的问题。从binary_exc
到base_exc
的转换是模棱两可的,因此异常处理程序不匹配。
不使用虚拟继承时,类型binary_exc<derived_exc1, derived_exc2>
的对象具有两个base_exc
子对象。它的布局是这样的:
+----------------------------------------+
| +--------------+ +--------------+ |
| | +----------+ | | +----------+ | |
| | | base_exc | | | | base_exc | | |
| | +----------+ | | +----------+ | |
| | derived_exc1 | | derived_exc2 | |
| +--------------+ +--------------+ |
| binary_exc<derived_exc1, derived_exc2> |
+----------------------------------------+
由于有两个base_exc
子对象,因此binary_exc
对象不能绑定到对base_exc
的引用。编译器将如何知道要将引用绑定到哪个base_exc
对象?
实际上,出于以下doesn't compile的完全相同的原因,它不起作用:
struct base {};
struct derived1 : base {};
struct derived2 : base {};
struct derived3 : derived1, derived2 {};
void foo(const base& b) {}
int main() {
derived3 d3;
foo(d3);
}
解决方案是使用虚拟继承:
struct base_exc : std::runtime_error
{
base_exc(const std::string& s): std::runtime_error(("base_exc: " + s).c_str()){}
};
struct derived_exc1 : virtual base_exc // <--- NOTE: added virtual keyword
{
derived_exc1(const std::string& s): base_exc(("derived_exc1: " + s).c_str()){}
};
struct derived_exc2 : virtual base_exc // <--- NOTE: added virtual keyword
{
derived_exc2(const std::string& s): base_exc(("derived_exc2: " + s).c_str()){}
};
template <typename T1, typename T2>
struct binary_exc: T1, T2
{
binary_exc(const std::string& s): base_exc(s), T1(s), T2(s){} // <-- NOTE: added call to base_exc constructor
};
使用虚拟继承,binary_exc
将只有一个base_exc
子对象。它将像这样布置:
+------------------------------------------------+
| +----------+ +--------------+ +--------------+ |
| | base_exc | | derived_exc1 | | derived_exc2 | |
| +----------+ +--------------+ +--------------+ |
| binary_exc<derived_exc1, derived_exc2> |
+------------------------------------------------+
由于只有一个base_exc
子对象,因此转换不再是模棱两可的,因此可以将binary_exc
对象绑定到对base_exc
的引用。
请注意,因为必须使用binary_exc
来初始化base_exc
,所以至少一个模板类型参数必须是从base_exc
派生的类。您可以使用一些SFINAE技巧来避免这种情况,但这是另一个问题。
答案 1 :(得分:1)
您应按照this Boost.Exception指南中所述使用虚拟继承。特别是在您的情况下,您实际上需要从base_exc
派生。
这样,在尝试将具体异常类型强制转换为base_exc
时,可以避免歧义。