C++ 中模板化异常 class 的多重继承
Multiple inheritance of a templated exception class in C++
为什么:
#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
我想要实现的目标:我想为我的代码中的某些异常设置两个 'orthogonal' class 化标准,例如一个基于代码中的位置 (library1_exc
, library2_exc
, ...) 和一个基于错误类别的错误 (myobject1isoutofbounds_exc
, myobject2isbroken_exc
, ..).
可以使用 throw binary_exc<library2_exc, myobject1isoutofbounds_exc>(msg)
之类的方式抛出这些异议,我可以使用以下任一方式捕获它们:
- 第一个导出class
catch(library2_exc const& e)
- 第二个导出class
catch(myobject1isoutofbounds_exc const& e)
- 基础class
catch(base_exc const& e)
我的代码在前两个之上 - 用派生的 classes 捕捉 - 工作正常,但最后一个没有。为什么?这里有反模式吗?
注意:
- 我读了 Exception multiple inheritance and https://www.boost.org/doc/libs/1_62_0/libs/exception/doc/using_virtual_inheritance_in_exception_types.html,但我无法弄清楚 if/how 它们与我的问题有关,尤其是与模板的使用有关。
- 我也完全可以不使用模板,但我仍然有兴趣了解为什么上面的代码不起作用。
- 对
binary_exc
使用虚拟继承会产生相同的结果。 (编辑:我写票的意思是我试过了struct binary_exc: virtual T1, virtual T2
)
- 在我看来,我的问题概括为 N>2 继承,但让我们从 2 开始。
您应该按照 this Boost.Exception 指南中的描述使用虚拟继承。特别是在您的情况下,您需要从 base_exc
.
几乎派生
这样,您就可以避免在尝试将具体异常类型转换为 base_exc
时出现歧义。
您链接的 boost 文档正是您的问题所在。从 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
.[=32= 的引用]
请注意,因为 binary_exc
需要初始化 base_exc
,所以至少有一个模板类型参数必须是从 base_exc
派生的 class。您可以使用一些 SFINAE 技巧来避免这种情况,但这是另一个问题。
为什么:
#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
我想要实现的目标:我想为我的代码中的某些异常设置两个 'orthogonal' class 化标准,例如一个基于代码中的位置 (library1_exc
, library2_exc
, ...) 和一个基于错误类别的错误 (myobject1isoutofbounds_exc
, myobject2isbroken_exc
, ..).
可以使用 throw binary_exc<library2_exc, myobject1isoutofbounds_exc>(msg)
之类的方式抛出这些异议,我可以使用以下任一方式捕获它们:
- 第一个导出class
catch(library2_exc const& e)
- 第二个导出class
catch(myobject1isoutofbounds_exc const& e)
- 基础class
catch(base_exc const& e)
我的代码在前两个之上 - 用派生的 classes 捕捉 - 工作正常,但最后一个没有。为什么?这里有反模式吗?
注意:
- 我读了 Exception multiple inheritance and https://www.boost.org/doc/libs/1_62_0/libs/exception/doc/using_virtual_inheritance_in_exception_types.html,但我无法弄清楚 if/how 它们与我的问题有关,尤其是与模板的使用有关。
- 我也完全可以不使用模板,但我仍然有兴趣了解为什么上面的代码不起作用。
- 对
binary_exc
使用虚拟继承会产生相同的结果。 (编辑:我写票的意思是我试过了struct binary_exc: virtual T1, virtual T2
) - 在我看来,我的问题概括为 N>2 继承,但让我们从 2 开始。
您应该按照 this Boost.Exception 指南中的描述使用虚拟继承。特别是在您的情况下,您需要从 base_exc
.
这样,您就可以避免在尝试将具体异常类型转换为 base_exc
时出现歧义。
您链接的 boost 文档正是您的问题所在。从 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
.[=32= 的引用]
请注意,因为 binary_exc
需要初始化 base_exc
,所以至少有一个模板类型参数必须是从 base_exc
派生的 class。您可以使用一些 SFINAE 技巧来避免这种情况,但这是另一个问题。