更多 C++ 惯用法/构造跟踪器
外观
识别在构造函数的初始化列表中初始化两个或多个对象时抛出相同异常类型的那个数据成员
构造跟踪器
当在构造函数的初始化列表中初始化两个或多个对象,并且它们都可能抛出相同的异常(std::exception)时,跟踪哪个对象失败成为一个棘手的问题,因为只有一个 try 块可以包围初始化列表。这样的 try 块有一个特殊的名称,叫做 "构造函数 try 块",它实际上是一个 "函数 try 块"。
构造跟踪器惯用法使用一种简单的技术来跟踪初始化列表中对象的成功构造。当对象的构造函数一个接一个地成功完成时,一个计数器简单地增加。它巧妙地使用括号运算符在对构造函数的调用之间插入计数器增量,这一切对类的用户来说都是不可见的。
#include <iostream>
#include <stdexcept>
#include <cassert>
struct B {
B (char const *) { throw std::runtime_error("B Error"); }
};
struct C {
C (char const *) { throw std::runtime_error("C Error"); }
};
class A {
B b_;
C c_;
enum TrackerType { NONE, ONE, TWO };
public:
A( TrackerType tracker = NONE)
try // A constructor try block.
: b_((tracker = ONE, "hello")) // Can throw std::exception
, c_((tracker = TWO, "world")) // Can throw std::exception
{
assert(tracker == TWO);
// ... constructor body ...
}
catch (std::exception const & e)
{
if (tracker == ONE) {
std::cout << "B threw: " << e.what() << std::endl;
}
else if (tracker == TWO) {
std::cout << "C threw: " << e.what() << std::endl;
}
throw;
}
};
int main (void)
{
try {
A a;
}
catch (std::exception const & e) {
std::cout << "Caught: " << e.what() << std::endl;
}
return 0;
}
双圆括号是括号运算符用于将赋值放置到跟踪器中的方式。此惯用法关键依赖于 B 和 C 的构造函数至少接受一个参数。如果类 B 或 C 不接受参数,那么需要编写一个适配器类,使适配器类接受一个虚拟参数并调用 B 和 C 的默认构造函数。可以使用 更多 C++ 惯用法/参数化基类 惯用法使用自下而上的混合技术编写这样的适配器。适配器类也可以完全封装在类 A 内部。在 conconstructor 类 A 中,跟踪器参数具有默认值,因此它不会打扰用户。