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

问题描述:

我一直在用我无法真正理解的析构函数调用顺序挣扎。C++ 11 shared_ptr对象的本地静态成员变量销毁顺序

假设我们有如下定义:

#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) 

有人可以解释为什么在的TestClass对象的析构函数结束之前被破坏DummyClass内部对象,在该特定情况? 我发现gcc 5.2.0使用-std = gnu ++ 11和clang 3.8.0与-std = C++ 11一致的行为,但找不到引用此示例的任何特定文档。

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

+0

是定义'DummyClass的TestClass ::假(“外”);'和'的std :: shared_ptr的 MTEST;'在同一个翻译单元(* .cpp文件),或者是不同的?如果同一个,哪一个是第一个? – aschepler

+0

在本例中,所有内容都位于同一翻译单元中。顺序如前所述,所以'DummyClass TestClass :: dummy(“outer”);'出现在'std :: shared_ptr之前 mTest;' –

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

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

  • 如果整个初始化可被认为是恒定的表达,这是初始化发生在其他事情之前。 (不适用于您的示例中的任何内容。)否则,

  • 命名空间成员和静态类成员保证在调用同一个翻译单元中的任何函数之前的某个点处开始初始化。 (在大多数的实现中,如果我们忽略了动态库加载,所有这些发生main开始之前,在你的例子,因为main是在同恩,我们知道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. 要完成初始化的最后一个静态对象是TestClass::objects()的0123'本地位于上面的步骤8,因此它首先被销毁。其析构函数体输出"~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. 最后,在上面的第2步中,第一个完成初始化的静态对象是TestClass::dummy,所以它最后被销毁。的析构函数DummyClass::~DummyClass体输出"~DummyClass\n"。该程序已完成。

所以,你的两个例子之间最大的区别是,在TestClass破坏被延迟,直到shared_ptr被破坏 - 这其实并不重要,在事物的TestClass创建方案时。由于shared_ptr在第二个示例中的“内部”DummyClass之前创建,因此在“内部”对象消失后发生其销毁,从而导致该未定义行为。

如果这是你遇到了,需要解决实际问题的简单化,你可以尝试添加类似

class TestClass { 
    // ... 
public: 
    class ForceInit { 
     ForceInit() { TestClass::objects(); } 
    }; 
    // ... 
}; 

// ... 
TestClass::ForceInit force_init_before_mTest; 
std::shared_ptr<TestClass> mTest; 
+0

事实上,这只是一个相当复杂的情况的简化: 我有一个头只有类定义,它必须持有'this'指针的派生类对象的静态列表。这些指针是通过ctor添加的,并在达到dtor时被删除。显然,问题在于销毁最后一个对象时。 列表保持一个静态方法里面,并通过它访问 –