57

我可以控制静态对象被破坏的顺序吗?有什么方法可以强制执行我想要的订单吗?例如,以某种方式指定我希望某个对象最后被销毁,或者至少在另一个静态对象之后被销毁?

4

9 回答 9

62

静态对象以与构造相反的顺序被破坏。而且施工顺序很难控制。您唯一可以确定的是,在同一个编译单元中定义的两个对象将按照定义的顺序构造。其他任何事情或多或少都是随机的。

于 2009-01-22T15:35:17.890 回答
35

对此的其他答案坚持认为这是不可能的。根据规范,他们是对的——但是有一个技巧可以让你做到这一点。

只创建一个类或结构的单个静态变量,其中包含您通常会创建静态变量的所有其他内容,如下所示:

class StaticVariables {
    public:
    StaticVariables(): pvar1(new Var1Type), pvar2(new Var2Type) { };
    ~StaticVariables();

    Var1Type *pvar1;
    Var2Type *pvar2;
};

static StaticVariables svars;

您可以按您需要的任何顺序创建变量,更重要的是,在构造函数和析构函数中以您需要的任何顺序销毁StaticVariables它们。为了使其完全透明,您也可以创建对变量的静态引用,如下所示:

static Var1Type &var1(*svars.var1);

瞧——完全控制。:-) 也就是说,这是额外的工作,通常是不必要的。但是当必要时,了解它是非常有用的。

于 2009-01-22T19:49:05.927 回答
13

静态对象的销毁顺序与它们构造的相反(例如,第一个构造的对象最后销毁),您可以使用条款 47 中描述的技术来控制静态对象的构造顺序,Meyers 的《Effective C++ 》一书中的“确保在使用全局对象之前对其进行初始化” 。

例如,以某种方式指定我希望某个对象最后被销毁,或者至少在另一个静态对象之后被销毁?

确保它在其他静态对象之前构造。

如何控制施工顺序?并非所有的静态都在同一个 dll 中。

我将忽略(为简单起见)它们不在同一个 DLL 中的事实。

我对 Meyers 的第 47 条(长 4 页)的解释如下。假设你的全局是在这样的头文件中定义的......

//GlobalA.h
extern GlobalA globalA; //declare a global

...像这样向该包含文件添加一些代码...

//GlobalA.h
extern GlobalA globalA; //declare a global
class InitA
{
  static int refCount;
public:
  InitA();
  ~InitA();
};
static InitA initA;

这样做的效果是任何包含 GlobalA.h 的文件(例如,定义第二个全局变量的 GlobalB.cpp 源文件)都将定义 InitA 类的静态实例,该实例将在该类中的任何其他内容之前构建源文件(例如在你的第二个全局变量之前)。

这个 InitA 类有一个静态引用计数器。当第一个 InitA 实例被构造时,现在保证在你的 GlobalB 实例被构造之前,InitA 构造函数可以做它必须做的任何事情来确保 globalA 实例被初始化。

于 2009-01-22T15:46:49.690 回答
12

简短的回答:一般来说,不会。

稍微长一点的答案:对于单个翻译单元中的全局静态对象,初始化顺序是从上到下,销毁顺序正好相反。几个翻译单元之间的顺序是不确定的。

如果您确实需要特定订单,则需要自己制作。

于 2009-01-22T15:36:21.070 回答
5

在标准 C++ 中没有办法做到这一点,但如果您对特定的编译器内部有很好的工作知识,它可能可以实现。

在 Visual C++ 中,指向静态初始化函数的指针位于.CRT$XI段(对于 C 类型静态初始化)或.CRT$XC段(对于 C++ 类型静态初始化)。链接器收集所有声明并按字母顺序合并它们。您可以通过使用在适当的段中声明对象来控制静态初始化发生的顺序

#pragma init_seg

例如,如果您希望在文件 B 之前创建文件 A 的对象:

文件 A.cpp:

#pragma init_seg(".CRT$XCB")
class A{}A;

文件 B.cpp:

#pragma init_seg(".CRT$XCC")
class B{}B;

.CRT$XCB之前被合并.CRT$XCC。当 CRT 遍历静态 init 函数指针时,它将在文件 B 之前遇到文件 A。

在 Watcom 中,段是 XI,#pragma initialize 的变化可以控制构造:

#pragma initialize before library
#pragma initialize after library
#pragma initialize before user

...有关更多信息,请参阅文档

于 2010-05-05T12:42:18.720 回答
4

阅读:
SO 初始化顺序

SO解决初始化问题的顺序

于 2009-01-22T20:31:51.567 回答
0

不,你不能。您永远不应该依赖静态对象的构造/破坏。

您始终可以使用单例来控制全局资源的构建/销毁顺序。

于 2009-01-22T15:35:14.567 回答
0

你真的需要之前初始化变量main吗?

如果您不这样做,您可以使用一个简单的习语来轻松地实际控制构造和销毁的顺序,请参见此处:

#include <cassert>

class single {
    static single* instance;

public:
    static single& get_instance() {
        assert(instance != 0);
        return *instance;
    }

    single()
    // :  normal constructor here
    {
        assert(instance == 0);
        instance = this;
    }

    ~single() {
        // normal destructor here
        instance = 0;
    }
};
single* single::instance = 0;

int real_main(int argc, char** argv) {
    //real program here...

    //everywhere you need
    single::get_instance();
    return 0;
}

int main(int argc, char** argv) {
    single a;
    // other classes made with the same pattern
    // since they are auto variables the order of construction
    // and destruction is well defined.
    return real_main(argc, argv);
}

它不会阻止您实际尝试创建该类的第二个实例,但如果您这样做,断言将失败。根据我的经验,它工作正常。

于 2015-02-18T10:41:45.583 回答
0

您可以通过使用 astatic std::optional<T>而不是T. 只需像使用变量一样初始化它,使用间接并通过分配std::nullopt(或对于 boost, boost::none)来销毁它。

它与具有预分配内存的指针不同,我猜这就是您想要的。因此,如果您销毁它并(可能在很久以后)重新创建它,您的对象将具有相同的地址(您可以保留),并且您当时无需支付动态分配/释放的成本。

boost::optional<T>如果您没有std::/ ,请使用std::experimental::

于 2016-11-25T19:45:01.257 回答