控制静态对象构造函数的顺序

Posted

技术标签:

【中文标题】控制静态对象构造函数的顺序【英文标题】:Controlling the order of static objects' constructor 【发布时间】:2012-03-17 06:45:45 【问题描述】:

我正在用 c++11 编写一个微型内核,并且有两个具有相同类型的实例,必须在创建任何其他静态对象之前构建它们。

我写的代码如下:

// test.hpp
class test 
  // blahblah...
;

// test.cpp
typedef char fake_inst[sizeof(test)] __attribute__((aligned(alignof(test))));

fake_inst inst1;
fake_inst inst2;

// main.cpp
extern test inst1;
extern test inst2;

int kmain() 
    // copy data section

    // initialize bss section

    new (&inst1) test();
    new (&inst2) test();

    // call constructors in .init_array

    // kernel stuffs

它按预期构建和工作,没有警告消息,但不是 LTO。

我收到大量抱怨类型匹配的警告消息,我想知道是否有解决方法,因为它让我难以找到其他“真实”警告或错误消息。

有什么建议吗?

【问题讨论】:

真正想做什么?在下面对 Pubby 的回答的 cmets 中,您说您要初始化多个相同类型的对象。对我来说,您似乎希望在让实现调用其他构造函数之前初始化与 C++ 支持相关的结构,例如堆。但这听起来不像你正在尝试的。另外,您会收到关于哪种“类型匹配”的警告? @Potatoswatter 很多 warning: type of ‘xxx’ does not match original declaration [enabled by default] 的东西。这是因为实际的类型不是 test 而是 char[]。我以为我可以禁用静态构造函数,但似乎没有办法做到这一点。 啊,我明白了。你应该做的是char fake_inst1[ sizeof (test) ]; test *const inst1 = reinterpet_cast< test * >( fake_inst1 ); 禁用静态构造函数的方法是将变量放入包装函数中。只有第一次调用包装器时才会调用构造函数。这是非常惯用的,并且总是优于将全局置于命名空间范围内。但是,在您的情况下,您需要确保在运行时支持用于实现“仅第一次”功能的多线程操作。 无论您是否找到抑制警告的方法,恕我直言,这是唯一真正正确处理具有非平凡 c' 的全局/静态对象的方法托/托。这样,您不仅可以控制它们的初始化/销毁顺序,还可以实际控制程序流程。例如,您可以从它们的初始化代码中抛出异常并适当地捕获它们。所有这一切都不会影响性能。 【参考方案1】:

你能使用 GCC 的 init_priority 属性吗?

Some_Class  A  __attribute__ ((init_priority (2000)));
Some_Class  B  __attribute__ ((init_priority (543)));

http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html#C_002b_002b-Attributes

【讨论】:

我已经读过这方面的内容,但是很难管理构造函数的顺序,因为优先级分布在源文件中。 @kukyakya 所以问题是先初始化这两个对象,还是对所有全局构造函数进行一般排序? @Potatoswatter 实际上它不仅仅是两个实例,而是同一类型的多个实例,所以我不能使用单例模式。其他静态对象的构造顺序无关紧要。【参考方案2】:

C++ 不提供管理跨多个文件的全局对象的初始化顺序的方法。如果您需要如此严格地管理这些对象的初始化顺序,那么我强烈建议您不要将它们设为全局对象。使它们成为包含静态对象并返回指向它们的指针的全局函数。

但即便如此,这也比完全手动初始化更危险。只需向需要它们的人提供一些指向这些对象的指针(最好不是全局的),就可以了。

【讨论】:

我考虑使用全局函数返回指向函数范围静态对象的引用或指针,但编译器生成获取/释放内容以创建实例,获取/释放内容使用内核互斥锁实现,可以'不用于内核初始化。 @kukyakya:那么您将不得不手动初始化这些对象。这是确保没有内核互斥锁的唯一方法。【参考方案3】:

大概是这样的?

// ... .h
template<typename T>
union FakeUnion 
  FakeUnion() 
  ~FakeUnion() 

  T inst;
;

extern FakeUnion<test> inst1_;
extern FakeUnion<test> inst2_;
static constexpr test& inst1 = inst1_.inst;
static constexpr test& inst2 = inst2_.inst;
// ... .h end

// ... .cpp
FakeUnion<test> inst1_;
FakeUnion<test> inst2_;
// ... .cpp end

main 内,您可以说new (&amp;inst1) test;。它现在不应再发出有关类型不一致违规的警告,因为与您的代码不同,此代码不包含在不同文件中具有不同类型的变量。

【讨论】:

虽然这确实解决了名称冲突,但并不能确保 inst1inst2 的构造函数在任何其他函数之前被调用。 @Matt 如果他在 main 中以新位置调用它们,它确实可以确保在 main 之后以受控方式调用所有其他 ctor。 你能告诉我这里发生了什么“魔法”吗? IE。为什么我们需要一个工会,为什么工会有 ctor/dtor?【参考方案4】:
// in an .h file

typedef char fake_inst[sizeof(test)] __attribute__((aligned(__alignof__(test))));

extern fake_inst fake_inst1;
extern fake_inst fake_inst2;

inline test& f_inst1()  return *reinterpret_cast<test*>(fake_inst1); 
inline test& f_inst2()  return *reinterpret_cast<test*>(fake_inst2); 

// and for readability

static test& inst1 (f_inst1());
static test& inst2 (f_inst2());

希望inst1f_inst1() 都能得到优化。

【讨论】:

这正是我正在使用的方式,但我想知道是否有更整洁的方式。我想要像 std::coutstd::cin 这样的全局对象,因为它看起来比实例返回函数更具可读性。 也许尝试一个全局引用变量,初始化为inst1的retval?这也是优化的(在 msvc 中) 全局变量的问题在于它们的初始化顺序,这就是我们要解决的... @n.m.:正确的点。但在这种特殊情况下,没关系。引用本身是一个全局变量,在main之前初始化为占位符。

以上是关于控制静态对象构造函数的顺序的主要内容,如果未能解决你的问题,请参考以下文章

静态代码块构造代码块构造函数以及Java类初始化顺序

实例构造函数与静态构造函数执行顺序

构造函数顺序

构造函数,构造代码块和静态代码块的先后顺序和运用

java中静态代码块构造代码块构造方法main函数的执行顺序?

java学习之静态块显示初始化块构造函数this在构造函数中的调用,区别联系与调用顺序