std::unique_ptr的删除顺序是否有保证?

7

我正在制作一个控制我的应用程序的全局单例,我希望子系统按特定顺序启动和关闭。

class App
{
public:
    App();
    ~App();

    void start();
    void run();
    void shutdown();

private:
    std::unique_ptr<DisplayManager> displayManager;
    std::unique_ptr<Renderer> renderer;
};

构造函数按正确顺序创建指针。
App::App()
{
    displayManager = std::unique_ptr<DisplayManager>(new DisplayManager);
    renderer = std::unique_ptr<Renderer>(new Renderer);
}

我希望unique_ptrs按相反的顺序被释放。 std::unique_ptr是否保证以此顺序释放内存?
我考虑过将所有管理器作为全局单例,但如果我可以使其工作,我认为这种方式会更好。
编辑:已经有人指出实际问题是实例变量成员的销毁顺序。在这种情况下,是否有担保的顺序?

12
这与unique_ptr的任何“保证”无关,而是涉及数据成员析构函数调用的顺序。 - juanchopanza
1
在类中声明顺序的相反顺序。在你的代码中,renderer将首先执行,然后是displayManager。无论你如何在初始化列表中(没有初始化列表,所以默认构造函数优先)或稍后在构造函数体中更改它们的顺序,你的智能指针都将按照成员声明顺序进行构造。销毁也将是同样可预测的相反顺序。 - WhozCraig
2
@WhozCraig:这个答案怎么出现在评论区了?! - Lightness Races in Orbit
2
请注意,如果您想明确拥有对象的销毁顺序,可以在析构函数中或其他任何地方调用unique_ptr上的reset()来实现。即使当前的销毁顺序对您有效,这也可能不是一个坏主意。 - Benjamin Lindley
1
由于问题已经改变了多次,而当我要回答这个问题时,它又再次改变了。现在,标题和最后一句话实际上是不同的问题。此外,在这一点上,我能提供的任何答案都已经被您和Nathan通过精细的编辑组合起来了,我的补充也就没有可能了。它可能曾经是一个可行的答案,但现在已经不是了。我一直在寻找重复内容,直到标题再次更改,然后我放弃了。 - WhozCraig
显示剩余2条评论
2个回答

15

std::unique_ptr不能控制其析构函数的调用时间。相反,它被声明的位置决定了它被销毁的顺序。

类成员按照在类体中声明的顺序进行构造,并以相反的顺序进行销毁。因此,在您的情况下,当构造一个App时,首先构造displayManager,然后构造renderer。当App实例被销毁时,首先销毁renderer,然后销毁displayManager


另外要注意的是

App::App()
{
    displayManager = std::unique_ptr<DisplayManager>(new DisplayManager);
    renderer = std::unique_ptr<Renderer>(new Renderer);
}

您正在为默认构造的unique_ptr分配值。 您需要使用成员初始化列表,例如:
App::App(): displayManager(new DisplayManager), renderer(new Renderer) {}
// or if you want to be in the don't use new camp
App::App(): displayManager(std::make_unique<DisplayManager>()), renderer(std::make_unique<Renderer>()) {}

如果您不想默认构造指针,然后再给它们赋值。


初始化列表是我的最初想法,但我担心构造顺序。我的想法是正确的吗?初始化列表是否按照头文件中声明类成员的顺序初始化,而不是按照初始化列表的顺序? - Xeronate
1
@MProgrammer 成员按照它们在类体中出现的顺序构造,而不管它们在成员初始化列表中如何初始化。 - NathanOliver
@MProgrammer:没错。一个好的编译器会在两者不同时警告你。 - Lightness Races in Orbit
“你需要”这个说法有点强烈了吧?虽然确实应该这样做,但现在使用 op= 就可以完成任务了。 - Lightness Races in Orbit
@LightnessRacesinOrbit 你又来了 ;-). 现在应该更好了。 - NathanOliver
此外,如果这基本上是PImpl惯用语,您可以将这些“std::unique_ptr”声明为“const”,以声明它们被构造时持有某些内容,但它们永远不会指向其他任何东西,因此所拥有数据的生命周期就是“App”实例的生命周期。 - Ben

10

是的,销毁顺序得到保证。

每个删除器在所属的std::unique_ptr被销毁时被立即调用ref,当它们一起超出作用域时,std::unique_ptr按其构造的相反顺序被销毁ref

然而,这个顺序与您在App::App()中分配内存的顺序没有直接关系 - 您可以交换它们而不会发生任何改变。重要的是Appstd::unique_ptr的声明顺序。

因此,虽然销毁顺序得到保证,但可能不是您期望的顺序。


1
是的,我从你的每篇帖子中都得到了两个不同的收获。谢谢。 - Xeronate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接