抛出的(默认)构造函数中的异常保证应该是什么?
What should be the exception guarantee in a (default) constructor that throws?
我知道构造函数可以抛出异常,当发生不好的事情时,这可能是一件好事。但是当构造函数抛出时,假设构造函数中的所有资源都得到了适当的管理(例如使用 RAII),哪一个是确切的异常保证(基本,强)?
详细地说,我正在记录我的代码并编写每个成员函数的异常保证(并尝试编写安全异常代码)。
例如,如果我有这样的 class:
struct A
{
std::string s;
A()
{
std::vector<int> v(5);
s = "some text";
/* do a lot of fascinating things */
if (error)
throw 1;
}
};
当构造函数抛出时,调用了v
和s
的析构函数,对吧?因此,由于 std::vector
和 std::string
的析构函数,A
的构造函数不会泄漏任何资源,因此它至少提供了基本保证。我说得对吗?
我的问题是:我能说这个构造函数提供了强有力的保证吗?
另外,是否值得记录构造函数的保证?
我的猜测:确实有很强的保证。由于对象在尝试构造它之前不存在,如果构造函数失败,无论如何都不会创建对象,那么操作(构造对象)没有效果,一切都在构造函数开始之前。
如果我猜对了:
- 当构造函数只提供基本保证而不提供强保证时?
强保证要求如果构造函数抛出,程序的(逻辑)状态不会改变(除了有异常需要处理)。这意味着如果在任何失败情况下没有通过引用或指针传递给它的对象、全局变量等被更改(或者任何确实发生的更改在构造函数被回滚之前被回滚),则构造函数满足强异常保证。左)。
您的示例代码中的构造函数不适用于任何此类对象,因此它确实提供了强有力的保证。
仅提供基本保证的构造函数示例是
struct foo {
foo(int &x) : some_resource(10) {
++x;
if(x % 2 == 0) {
throw "something";
}
}
std::vector<int> some_resource;
};
在这种情况下,基本保证得到满足——some_resource
在所有情况下都被清除——但强保证不是因为 x
在抛出异常时保持更改。
至于文档,那是一种见仁见智的问题,所以 YMMV。当然,我的目标通常是提供合理可能的最强有力的保证,并且如果我确信我可以永远保持这种保证,则记录一个函数满足强保证或不抛出保证。没有必要记录基本保证,因为所有功能都应该提供它。一个不提供它的功能有一个错误。
你是对的。构造函数修改的对象只有s
和v
,在调用构造函数前不存在,异常退出后也不存在。因此没有可观察到的副作用,并且构造函数提供了强大的异常保证。
什么时候施工方可能只提供基本保障?可能人为的例子:
class A {
public:
A() {
printf("A is being constructed\n");
throw std::runtime_error("oh no!");
}
};
很明显基本保证得到支持,但由于有副作用,所以不支持强保证。 (产生副作用的另一种方法是修改全局变量。)如果构造函数采用参数,则会发生更有趣的事情。另一个可能人为的例子:
class B {
public:
A(std::vector<int>&& v): v(std::move(v)), a() {}
private:
std::vector<int> v;
A a;
};
在这里,A
的构造函数在 B::v
已经初始化后抛出,因此后者被销毁。调用者仍然有一个有效的向量,但它现在是空的。基本保证仍然满足,因为所有对象都处于有效状态,但不是强保证。
我知道构造函数可以抛出异常,当发生不好的事情时,这可能是一件好事。但是当构造函数抛出时,假设构造函数中的所有资源都得到了适当的管理(例如使用 RAII),哪一个是确切的异常保证(基本,强)?
详细地说,我正在记录我的代码并编写每个成员函数的异常保证(并尝试编写安全异常代码)。
例如,如果我有这样的 class:
struct A
{
std::string s;
A()
{
std::vector<int> v(5);
s = "some text";
/* do a lot of fascinating things */
if (error)
throw 1;
}
};
当构造函数抛出时,调用了v
和s
的析构函数,对吧?因此,由于 std::vector
和 std::string
的析构函数,A
的构造函数不会泄漏任何资源,因此它至少提供了基本保证。我说得对吗?
我的问题是:我能说这个构造函数提供了强有力的保证吗? 另外,是否值得记录构造函数的保证?
我的猜测:确实有很强的保证。由于对象在尝试构造它之前不存在,如果构造函数失败,无论如何都不会创建对象,那么操作(构造对象)没有效果,一切都在构造函数开始之前。
如果我猜对了:
- 当构造函数只提供基本保证而不提供强保证时?
强保证要求如果构造函数抛出,程序的(逻辑)状态不会改变(除了有异常需要处理)。这意味着如果在任何失败情况下没有通过引用或指针传递给它的对象、全局变量等被更改(或者任何确实发生的更改在构造函数被回滚之前被回滚),则构造函数满足强异常保证。左)。
您的示例代码中的构造函数不适用于任何此类对象,因此它确实提供了强有力的保证。
仅提供基本保证的构造函数示例是
struct foo {
foo(int &x) : some_resource(10) {
++x;
if(x % 2 == 0) {
throw "something";
}
}
std::vector<int> some_resource;
};
在这种情况下,基本保证得到满足——some_resource
在所有情况下都被清除——但强保证不是因为 x
在抛出异常时保持更改。
至于文档,那是一种见仁见智的问题,所以 YMMV。当然,我的目标通常是提供合理可能的最强有力的保证,并且如果我确信我可以永远保持这种保证,则记录一个函数满足强保证或不抛出保证。没有必要记录基本保证,因为所有功能都应该提供它。一个不提供它的功能有一个错误。
你是对的。构造函数修改的对象只有s
和v
,在调用构造函数前不存在,异常退出后也不存在。因此没有可观察到的副作用,并且构造函数提供了强大的异常保证。
什么时候施工方可能只提供基本保障?可能人为的例子:
class A {
public:
A() {
printf("A is being constructed\n");
throw std::runtime_error("oh no!");
}
};
很明显基本保证得到支持,但由于有副作用,所以不支持强保证。 (产生副作用的另一种方法是修改全局变量。)如果构造函数采用参数,则会发生更有趣的事情。另一个可能人为的例子:
class B {
public:
A(std::vector<int>&& v): v(std::move(v)), a() {}
private:
std::vector<int> v;
A a;
};
在这里,A
的构造函数在 B::v
已经初始化后抛出,因此后者被销毁。调用者仍然有一个有效的向量,但它现在是空的。基本保证仍然满足,因为所有对象都处于有效状态,但不是强保证。