首页 > 解决方案 > 如果构造函数抛出 RAII 和成员?

问题描述

我之前曾在异常已关闭且内存分配失败意味着我们终止程序的环境中工作。现在处理异常我想知道以下内容的精确语义:

class Foo {
  std::unique_ptr<Bar> x;
  std::unique_ptr<Bar> y;
 public:
  Foo(): x{new Bar}, y{new Bar} {}
};

我的问题是在分配时new Bar抛出时会发生什么?y我假设x调用了析构函数,以便清理第一个分配。语言如何保证这一点?任何人都知道解释精确语义的标准引用吗?

标签: c++exceptionconstructorlanguage-lawyerobject-lifetime

解决方案


是的,所有完全构建的成员都将被销毁。您的对象不会处于任何“半生不死”状态。不会发生内存泄漏。

[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;没有它,祝你好运。


推荐阅读