我以前工作过的环境中,异常已经关闭,失败的内存分配意味着我们会杀死程序。现在,在处理异常时,我想知道以下内容的确切语义:
class Foo {
std::unique_ptr<Bar> x;
std::unique_ptr<Bar> y;
public:
Foo(): x{new Bar}, y{new Bar} {}
};我的问题是,当y被分配时,当new Bar抛出时会发生什么?我假设调用了x的析构函数,以便清理第一个分配。语言是如何保证这一点的?有谁知道标准中的一句话来解释确切的语义?
发布于 2020-02-29 23:15:49
是的,所有完全构造的成员都将被销毁。您的对象将不会处于任何类型的“半活动”状态。不会发生内存泄漏。
[except.ctor]/3:如果对象的初始化或销毁(不是通过委托构造函数)被异常终止,则为对象的每个直接子对象调用析构函数,对于完整对象,调用其初始化已完成的虚拟基类子对象(dcl.init)。子对象的销毁顺序与其构造的完成顺序相反。。。
我们可以自己演示这一点:
#include <memory>
#include <iostream>
struct Bar
{
Bar(const char* name, bool doThrow = false) : m_name(name)
{
if (doThrow)
{
std::cout << name << ": Bar() throwing\n";
throw 0;
}
std::cout << name << ": Bar()\n";
}
~Bar() { std::cout << m_name << ": ~Bar()\n"; }
private:
const char* m_name;
};
class Foo {
std::unique_ptr<Bar> x;
std::unique_ptr<Bar> y;
public:
Foo(): x{new Bar("A")}, y{new Bar("B", true)} {}
};
int main()
{
try
{
Foo f;
}
catch (...) {}
}
// g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
// A: Bar()
// B: Bar() throwing
// A: ~Bar()事实上,这是所谓的“智能指针”的主要好处之一:异常安全。如果x是一个原始指针,你会泄漏它所指向的东西,因为一个原始指针的销毁不会做任何事情。有了异常安全,你可以拥有RAII;没有它,祝你好运。
发布于 2020-02-29 23:25:00
如果您担心两个new Bar表达式在句柄初始化以保持它们的含义之前交错和抛出,那么标准不允许这样做。
First in intro.execution
12完整表达式为
的构成表达式
16在计算与下一个完整表达式相关的每个值计算和副作用之前,会对与完整表达式相关的每个值计算和副作用进行排序。
没有太多细节,x{new Bar}和y{new Bar}的整体都被认为是standardese wise认为的“完整表达式”(即使它们不是语法上的表达式)。我引用的两个段落指出,要么必须首先进行x (包括new Bar)的整个初始化,要么必须首先进行y的整个初始化。我们从class.base.init得知
13.3 -然后,非静态数据成员按照它们在类定义中声明的顺序进行初始化(同样与内存初始化器的顺序无关)。
因此,先完全初始化x,然后初始化y。因此,即使new Bar在初始化y时抛出异常,x也已经拥有了它应该持有的资源。在这种情况下,当异常被抛出时,except.ctor parageph 3中的言辞将应用于完全构造的x,并且它将被析构,从而释放资源。
发布于 2020-02-29 23:09:37
如果在对象构造过程中抛出异常,那么对象不会被构造,并且您不应该对任何成员的状态做出任何假设。这个对象根本是不可用的。在引发异常之前初始化的任何成员都将以与其构造相反的顺序被析构。
https://stackoverflow.com/questions/60466427
复制相似问题