【问题标题】:Multiple inheritance of a templated exception class in C++ [duplicate]C ++中模板化异常类的多重继承[重复]
【发布时间】:2019-03-20 02:10:21
【问题描述】:

为什么会这样:

#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_exc, myobject2isbroken_exc, .. )。

可以使用throw binary_exc&lt;library2_exc, myobject1isoutofbounds_exc&gt;(msg) 之类的东西抛出这些反对意见,我可以使用以下任何一种方法来捕捉它们:

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

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

注意:

【问题讨论】:

  • 我原以为编译器应该/会在 what 调用上至少输出一个模棱两可的警告/错误。请注意,coliru 上的 Clang 只是调用终止:coliru.stacked-crooked.com/a/df03fe2c07d6a80d
  • 取出模板,发生完全相同的事情
  • 虚拟继承没有同样的问题,看这里:wandbox.org/permlink/cpGIMADQBpGlPnkm
  • 完美@linuxfever!我的错误是我试图在base_exc 上使用虚拟继承 - 即struct binary_exc: virtual T1, virtual T2 - 而不是派生类 - 即struct derived_exc2 : virtual base_exc -。
  • 确实@AlanBirtles 这与stackoverflow.com/questions/11484691/… 基本上是相同的问题,我将其标记为重复。我没有认出它可能是因为我正在使用模板。

标签: c++ templates exception-handling multiple-inheritance virtual-inheritance


【解决方案1】:

您链接的 boost 文档是您的确切问题。从binary_excbase_exc 的转换不明确,因此异常处理程序不匹配。

当不使用虚拟继承时,binary_exc&lt;derived_exc1, derived_exc2&gt; 类型的对象有两个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
};

Live Demo

使用虚拟继承,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 技巧来避免这种情况,但这是另一个问题。

【讨论】:

【解决方案2】:

您应该按照this Boost.Exception 指南中的说明使用虚拟继承。特别是在您的情况下,您实际上需要从 base_exc 派生。

这样,您在尝试将具体异常类型转换为 base_exc 时避免了歧义。

【讨论】:

  • 谢谢@dhavenith!正如上面 cmets 中所解释的,我在错误的类上使用了 virtual 关键字。 @linuxfever 的更新代码为我指明了正确的方向。我可以接受这个答案,但你介意编辑它以包含代码的更新部分吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-14
  • 2020-06-08
相关资源
最近更新 更多