为什么这样做?
#include <exception>
#include <iostream>
#include <stdexcept>
#include <boost/exception/all.hpp>
struct foo_error : virtual boost::exception, public std::runtime_error
{
explicit foo_error(const char* what)
: std::runtime_error(what)
{ }
explicit foo_error(const std::string& what)
: std::runtime_error(what)
{ }
};
struct bar_error : virtual boost::exception, public std::runtime_error
{
explicit bar_error(const char* what)
: std::runtime_error(what)
{ }
explicit bar_error(const std::string& what)
: std::runtime_error(what)
{ }
};
struct abc_error : virtual foo_error, virtual bar_error
{
explicit abc_error(const char* what)
: foo_error(what), bar_error(what)
{ }
explicit abc_error(const std::string& what)
: foo_error(what), bar_error(what)
{ }
};
static void abc()
{
throw abc_error("abc error");
}
int main()
{
try
{
abc();
}
catch (const std::exception& e)
{
std::cerr << e.what();
}
}
由于从abc_error
到std::exception
的模糊转换,我认为这不应该编译。我错过了什么?我想出了继承图,我无法弄清楚为什么这个代码有效(箭头表示虚拟继承,而行表示非虚拟继承)。
std::exception std::exception
+ +
| |
| |
+ +
std::runtime_error std::runtime_error
+ +
| |
| +-->boost::exception<-+ |
+ | | +
foo_error+<-----+ +--->+bar_error
| |
| |
| |
+abc_error+
看起来abc_error
包含两个std::exception
个实例,因此catch
(或者我认为)应该无法将abc_error
投射到std::exception
}。还是应该呢?
更新
此刻我无法回答我自己的问题,所以我将继续在这里。我把问题缩小到了:
struct NonVirtualBaseBase { };
struct NonVirtualBase : NonVirtualBaseBase { };
struct VirtualBase { };
struct A : virtual VirtualBase, NonVirtualBase { };
struct B : virtual VirtualBase, NonVirtualBase { };
struct C : A, B { };
int main()
{
try
{
throw C();
}
catch (const VirtualBase& e)
{
return 1;
}
return 0;
}
上面的示例按预期工作,是一段非常精细的代码。如果我将catch (const VirtualBase& e)
替换为catch (const NonVirtualBase& e)
我认为理智且有意义,它会崩溃。 但如果我用catch (const NonVirtualBaseBase& e)
替换相同的行,它也有效,这对我来说似乎有些奇怪和错误。编译器错误?
答案 0 :(得分:2)
更新
正如OP指出的那样,这种解释并没有完全消除它,因为std::exception
不是使用虚拟继承派生的。我相信答案是,这实际上是未定义的行为,并且根本没有在编译时被捕获,因为throw
和catch
不需要知道彼此并且警告它们是否不兼容。< / p>
END UPDATE
答案是此层次结构使用* 虚拟继承 *从boost::exception
派生。
由于foo_error
和bar_error
都使用虚拟继承从boost::exception
继承,因此只有boost::exception
个基础在foo_error
之间共享和bar_error
的{{1}}个子对象。
在基类列表中的条目之前指定abc_error
时,这意味着此类作为最派生对象中的虚拟基类的所有出现实际上将引用同一实例。它专门用于避免此类设计中的歧义。
答案 1 :(得分:1)
为什么这样做?
对于一些松散定义的“工作”值。
me@mybox > g++ -o test test.cpp
me@mybox > ./test
terminate called after throwing an instance of 'abc_error'
Aborted (core dumped)
me@mybox >
答案 2 :(得分:1)
它必须编译,因为没有理由你不能在异常中有多重定义的基类,并且没有理由你不能在异常声明中的某个地方拥有该基类。事件abc
碰巧抛出一个特定的东西,而main
恰好抓住了一个特定的东西,并且那些东西是敌人,无法进行编译时检查。
它不会做的是正确捕捉异常,或者至少它不应该。但我可以看到特定编译器如何最终(错误地)执行此操作,因为异常声明如何匹配。