c ++ 11 local_ptr对象的本地静态成员变量销毁顺序

时间:2017-10-16 18:40:27

标签: c++11 global-variables shared-ptr lifetime static-variables

我一直在努力处理一个我无法理解的析构函数调用命令。

假设我们有以下定义:

#include <memory>
#include <iostream>

class DummyClass {
    std::string name;
public:
    DummyClass(std::string name) : name(name) { std::cout << "DummyClass(" << name << ")" << std::endl; }
    ~DummyClass() { std::cout << "~DummyClass(" << name << ")" << std::endl; }
};


class TestClass {
private:

    static DummyClass dummy;

    static DummyClass& objects() {
        static DummyClass dummy("inner");
        return dummy;
    }
public:
    TestClass() {
        std::cout << "TestClass" << std::endl;
        std::cout << "TestClass Objects is: " << &objects() << std::endl;
    }

    virtual ~TestClass() {
        std::cout << "~TestClass Objects is: " << &objects() << std::endl;
        std::cout << "~TestClass" << std::endl;
    }
};

DummyClass TestClass::dummy("outer");

现在,如果我按如下方式实例化TestClass

TestClass *mTest = nullptr;

int main() {
    mTest = new TestClass(); delete mTest;
    return 0;
}

获得的输出是我期望的输出:

DummyClass(outer)
TestClass
DummyClass(inner)
TestClass Objects is: 0x....
~TestClass Objects is: 0x....
~TestClass
~DummyClass(inner)
~DummyClass(outer)

但是,现在,如果我对mTest使用shared_ptr,例如:

std::shared_ptr<TestClass> mTest;

int main() {
    mTest = std::make_shared<TestClass>();
    return 0;
}

产生的输出是:

DummyClass(outer)
TestClass
DummyClass(inner)
TestClass Objects is: 0x....
~DummyClass(inner)
~TestClass Objects is: 0x....
~TestClass
~DummyClass(outer)

有人可以解释为什么DummyClass内部对象在TestClass对象析构函数结束之前被销毁,在这种特殊情况下? 我使用-std = gnu ++ 11和clang 3.8.0与-std = c ++ 11找到了gcc 5.2.0的一致行为,但是引用了这个例子找不到任何特定的文档。

编辑:澄清一下:上面的所有代码都是按照显示的顺序写在同一个翻译单元(* .cpp文件)中。它是一个用例的简化,我有一个只有头的类定义,它必须包含一个指向派生类对象的this指针的静态列表。这些指针通过ctor添加,并在到达dtor时删除。销毁最后一个对象时会触发此问题。该列表保存在静态方法中,并通过它访问以实现标题目标。

2 个答案:

答案 0 :(得分:1)

具有静态存储持续时间的所有对象(函数定义中的命名空间成员,静态类成员和static对象)的规则是:

  • 如果整个初始化可以被认为是一个常量表达式,那么初始化就会发生在其他任何事情之前。 (不适用于示例中的任何内容。)否则,

  • 保证命名空间成员和静态类成员在调用同一翻译单元中的任何函数之前的某个时刻开始初始化。 (在大多数实现中,如果我们忽略动态库加载,所有这些都会在main开始之前发生。在您的示例中,由于main位于同一个TU中,我们知道它们发生在main之前。)

  • 在同一个TU中定义的命名空间成员和静态类成员按其定义的顺序开始初始化。

  • 对于在不同TU中定义的命名空间成员和静态类成员,无法保证初始化顺序!

  • 函数内定义的静态对象在程序控制第一次达到定义时开始初始化(如果有的话)。

  • main返回或std::exit被调用时,所有具有静态存储持续时间的对象都会被破坏,与每个完成初始化时相反。

所以在你的第二个例子中:

  1. TestClass::dummy的初始化开始了。首先创建一个临时std::string,然后调用DummyClass::DummyClass(std::string)

  2. DummyClass构造函数执行std::string副本,然后输出"DummyClass(outer)\n"。临时std::string被销毁。 TestClass::dummy的初始化已完成。

  3. ::mTest的初始化开始了。这会调用std::shared_ptr<TestClass>::shared_ptr()

  4. shared_ptr构造函数将智能指针设置为null。 ::mTest的初始化已完成。

  5. main开始。

  6. std::make_shared调用最终会创建一个TestClass对象,并调用TestClass::TestClass()。此构造函数首先打印"TestClass\n",然后调用TestClass::objects()

  7. TestClass::objects()内,开始初始化本地对象dummy。再次创建一个临时std::string,并调用DummyClass::DummyClass(std::string)

  8. DummyClass构造函数执行std::string副本,然后输出"DummyClass(inner)\n"。临时std::string被销毁。 objects'dummy的初始化已完成。

  9. TestClass::TestClass()继续,打印"TestClass Objects is: 0x ... \n"。动态TestClass对象的初始化已完成。

  10. 返回mainmake_shared函数返回临时std::shared_ptr<TestClass>。移动分配从返回的临时移动到::mTest,然后临时被销毁。请注意,虽然TestClass对象与::mTest相关联,但它具有动态存储持续时间,而非静态存储持续时间,因此上述规则不适用于此。

  11. main返回。 C ++开始销毁具有静态存储持续时间的对象。

  12. 完成初始化的最后一个静态对象是上面步骤8中dummy的本地TestClass::objects(),因此它首先被销毁。它的析构函数体输出"~DummyClass(inner)\n"

  13. 在上面的步骤4中完成初始化的下一个对象是::mTest,因此接下来开始销毁。 ~shared_ptr析构函数最终会破坏拥有的动态TestClass对象。

  14. TestClass::~TestClass()析构函数体首先调用TestClass::objects()

  15. TestClass::objects()中,我们遇到了已经被破坏的函数 - 本地静态的定义,即未定义的行为!显然,你的实现只会返回对以前包含dummy的存储的引用,除了获取地址之外你没有对它做任何事情可能是件好事。

  16. TestClass::~TestClass()继续,输出"~TestClass Objects is: 0x ... \n"然后"~TestClass\n"

  17. ~shared_ptr ::mTest析构函数释放相关内存并完成。

  18. 最后,完成初始化的第一个静态对象是TestClass::dummy,在上面的步骤2中,所以它最后被销毁。 DummyClass::~DummyClass析构函数体输出"~DummyClass\n"。该计划已经完成。

  19. 因此,你的两个例子之间的最大区别在于TestClass破坏被推迟到shared_ptr被破坏之前 - 在{{1}的事物计划中它并不重要已创建。由于TestClass是在第二个示例中的“内部”shared_ptr之前创建的,因此在“内部”对象消失后会发生其破坏,从而导致未定义的行为。

    如果这是您遇到并需要修复的实际问题的简化,您可以尝试添加类似

    的内容
    DummyClass

答案 1 :(得分:0)

它与shared_ptr无关,而与模块(cc文件)中全局变量的销毁顺序无关。规范声明订单未定义,因此您不能假设静态内部对象将在另一个全局对象之后或之前销毁。如果您需要具有一致的销毁顺序,我建议您明确地处理它。