静态析构函数的顺序

时间:2017-11-23 14:53:44

标签: c++

如果类Foo具有静态成员变量Bar,我希望Bar的析构函数仅在Foo的析构函数的最后一个实例运行之后运行。下面的代码片段(gcc 6.3,clang 3.8)不会发生这种情况:

#include <memory>
#include <iostream>

class Foo;
static std::unique_ptr<Foo> foo;

struct Bar {
    Bar() {
        std::cout << "Bar()" << std::endl;
    }

    ~Bar() {
        std::cout << "~Bar()" << std::endl;
    }
};

struct Foo {
    Foo() {
        std::cout << "Foo()" << std::endl;
    }

    ~Foo() {
        std::cout << "~Foo()" << std::endl;
    }

    static Bar bar;
};
Bar Foo::bar;

int main(int argc, char **argv) {
    foo = std::make_unique<Foo>();
}

输出:

Bar()
Foo()
~Bar()
~Foo()

为什么破坏的顺序不是与建筑相反的? 如果~Foo()使用Foo::bar,则在删除后使用。

5 个答案:

答案 0 :(得分:11)

在C ++中,对象按出现的顺序构造,并以相反的顺序进行破坏。首先是foo构造,然后是bar构造,然后执行main,然后bar被破坏,然后foo。这是你看到的行为。出现切换是因为foo的构造函数没有构造Foo,它会构造一个空的unique_ptr,因此您无法看到Foo()在输出中。然后使用输出构建bar,并在main中在Foo构建之后创建实际的foo

答案 1 :(得分:2)

  

我希望Bar的析构函数只能在Foo的析构函数运行的最后一个实例之后才能运行。

不,作为Foo::bar数据成员,Foo独立于static std::unique_ptr<Foo> foo; // no Foo created here Bar Foo::bar; // Foo::bar is initialized before main(), => "Bar()" int main(int argc, char **argv) { foo = std::make_unique<Foo>(); // an instance of Foo is created, => "Foo()" } // objects are destroyed in the reverse order how they're declared // Foo::bar is defined after foo, so it's destroyed at first => "~Bar()" // foo is destroyed; the instance of Foo managed by it is destroyed too => "~Foo()" 的任何实例。

对于您展示的代码,

onEachFeature: function (feature, layer) {
    var popupContent = "<p>I started out as a GeoJSON " +
     feature.geometry.type + ", but now I'm a Leaflet vector!</p>";

    if (feature.properties && feature.properties.popupContent) {
      popupContent += feature.properties.popupContent;
    }
    $('#sidebar>.sidebar_content').html(popupContent);
    $('.custom-div').html(popupContent);
}

答案 2 :(得分:2)

这里的复杂性是代码没有检测foo的构造函数。会发生什么是foo首先被构造,而Foo::bar被构造。对make…unique的调用会构造一个Foo对象。然后main退出,两个静态对象按其构造的相反顺序销毁:Foo::bar被破坏,然后foofoo的析构函数会破坏它指向的Foo对象,即main中创建的对象。

答案 3 :(得分:1)

静态对象的生命周期完全基于其定义的顺序。编辑Bar::Bar()与调用Bar::~Bar()的时间相比,编辑器“不够”知道。

为了更好地说明问题,请考虑这个

class Foo;

struct Bar {
    Bar() {
        std::cout << "Bar()" << std::endl;
    }

    ~Bar() {
        std::cout << "~Bar()" << std::endl;
    }

    void baz() {}
};

struct Foo {
    Foo() {
        bar.baz();
        std::cout << "Foo()" << std::endl;
    }

    ~Foo() {
        std::cout << "~Foo()" << std::endl;
    }

    static Bar bar;
};

Foo foo;
Bar Foo::bar;

int main() {}

打印

Foo()
Bar()
~Bar()
~Foo()

std::unique_ptr在main中构建Foo::Foo()之后推迟Bar::Bar(),让人觉得编译器“知道”何时调用bar

TLDR静态对象的定义应晚于其依赖项。在定义std::unique_ptr<Foo>之前,定义Foo和定义 from("ftps://{{ftp.username}}@{{ftp.host}}/{{ftp.importDirectory}}?password=" + "{{ftp.password}}&readLock=changed&move={{ftp.processed}}" + "&moveFailed={{ftp.failed}}" + "&securityProtocol=SSL&execProt=P&execPbsz=0&passiveMode=true") .idempotentConsumer(header("camelFileAbsolutePath"), MemoryIdempotentRepository.memoryIdempotentRepository(200)) .to("bean:ftpConsumer?method=consumeMethod")

同样是一个错误。

答案 4 :(得分:0)

通常,您不应编写依赖于构造或破坏静态(或全局)数据的顺序的代码。这使得代码难以理解且不可维护(您可能更喜欢静态智能指针,或者 显式初始化或从main调用的启动例程。当您链接多个翻译单元时,未指定AFAIK订单。

请注意,GCC提供了init_priorityconstructor(带优先级)属性。我相信你应该避免使用它们。但是,__attribute__(constructor) is有用的inside插件,用于插件初始化。

在某些情况下,您还可以使用atexit(3)(至少在POSIX系统上)。我不知道这些注册函数是在析构函数之前还是之后调用的(我认为你不应该关心那个顺序)。