C++中模板化异常类的多重继承

Multiple inheritance of a templated exception class in C++

本文关键字:多重继承 异常 C++      更新时间:2023-10-16

为什么:

#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_exclibrary2_exc,...(,另一个基于错误类别(myobject1isoutofbounds_excmyobject2isbroken_exc,.(。

这些反对意见可以使用类似throw binary_exc<library2_exc, myobject1isoutofbounds_exc>(msg)的东西抛出,我将能够使用以下任一方法捕获它们:

  • 第一个派生类catch(library2_exc const& e)
  • 第二个派生类catch(myobject1isoutofbounds_exc const& e)
  • 基类catch(base_exc const& e)

使用我的前两个代码 - 使用派生类捕获 - 工作正常,但最后一个不能。为什么?这里有反模式吗?

请注意:

  • 我阅读了异常多重继承和 https://www.boost.org/doc/libs/1_62_0/libs/exception/doc/using_virtual_inheritance_in_exception_types.html,但我无法弄清楚它们是否/如何与我的问题相关,尤其是模板的使用。
  • 我也完全可以使用不使用模板,但我仍然有兴趣了解为什么上面的代码不起作用。
  • binary_exc使用虚拟继承会产生相同的结果。(编辑:当我写票时,我的意思是我尝试了struct binary_exc: virtual T1, virtual T2(
  • 在我看来,我的问题推广到 N>2 个继承,但让我们从 2 开始。

您链接的提升文档正是您的问题。 从binary_excbase_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对象?

事实上,它不起作用的原因与以下内容无法编译的原因完全相同:

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的引用。

注意,由于初始化base_exc需要binary_exc,因此至少有一个模板类型参数必须是派生自base_exc的类。 您可以使用一些SFINAE技巧来避免这种情况,但这是另一个问题。

您应该按照本 Boost.Exception 指南中所述使用虚拟继承。特别是在您的情况下,您需要虚拟地从base_exc派生。

这样,在尝试将具体异常类型强制转换为base_exc时,可以避免歧义。