控制静态对象构造函数的顺序
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 (&inst1) test;
。它现在不应再发出有关类型不一致违规的警告,因为与您的代码不同,此代码不包含在不同文件中具有不同类型的变量。
【讨论】:
虽然这确实解决了名称冲突,但并不能确保inst1
和 inst2
的构造函数在任何其他函数之前被调用。
@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());
希望inst1
和f_inst1()
都能得到优化。
【讨论】:
这正是我正在使用的方式,但我想知道是否有更整洁的方式。我想要像std::cout
或 std::cin
这样的全局对象,因为它看起来比实例返回函数更具可读性。
也许尝试一个全局引用变量,初始化为inst1
的retval?这也是优化的(在 msvc 中)
全局变量的问题在于它们的初始化顺序,这就是我们要解决的...
@n.m.:正确的点。但在这种特殊情况下,没关系。引用本身是一个全局变量,在main
之前初始化为占位符。以上是关于控制静态对象构造函数的顺序的主要内容,如果未能解决你的问题,请参考以下文章