为什么静态数据成员不能被初始化?
我想在加载时注册一堆类的工厂。我的策略是利用静态初始化来确保在main()开始之前,工厂已经准备好了。当我动态地链接我的库时,这个策略似乎工作,但是当我静态链接时,这种策略不起作用;当我静态链接时,只有一些静态数据成员被初始化。为什么静态数据成员不能被初始化?
假设我的工厂生产汽车。我有CarCreator类可以实例化少数汽车,但不是全部。我希望工厂能够收集所有这些CarCreator类,以便寻找新车的代码无需知道谁将参与实际施工就可以去工厂。
所以我有
CarTypes.hpp
enum CarTypes
{
prius = 0,
miata,
hooptie,
n_car_types
};
MyFactory.hpp
class CarCreator
{
public:
virtual Car * create_a_car(CarType) = 0;
virtual std::list<CarTypes> list_cars_I_create() = 0;
};
class MyFactory // makes cars
{
public:
Car * create_car(CarType type);
void factory_register(CarCreator *)
static MyFactory * get_instance(); // singleton
private:
MyFactory();
std::vector< CarCreator * > car_creator_map;
};
MyFactory.cpp
MyFactory:: MyFactory() : car_creator_map(n_car_types);
MyFactory * MyFactory::get_instance() {
static MyFactory * instance(0); /// Safe singleton
if (instance == 0) {
instance = new MyFactory;
}
return instance;
}
void MyFactory::factory_register(CarCreator * creator)
{
std::list<CarTypes> types = creator->list_cars_I_create();
for (std::list<CarTypes>::const_iteator iter = types.begin();
iter != types.end(); ++iter) {
car_creator_map[ *iter ] = creator;
}
}
Car * MyFactory::create_car(CarType type)
{
if (car_creator_map[ type ] == 0) { // SERIOUS ERROR!
exit();
}
return car_creator_map[ type ]->create_a_car(type);
}
...
然后,我将有具体的汽车和汽车专用创:
Miata.cpp
class Miata : public Car {...};
class MiataCreator : public CarCreator {
public:
virtual Car * create_a_car(CarType);
virtual std::list<CarTypes> list_cars_I_create();
private:
static bool register_with_factory();
static bool registered;
};
bool MiataCreator::register_with_factory()
{
MyFactory::get_instance()->factory_register(new MiataCreator);
return true;
}
bool MiataCreator::registered(MiataCreator::register_with_factory());
...
重申:动态链接库我,MiataCreator ::注册将得到初始化,静态链接我的库,它不会被初始化。
使用静态构建,当有人去工厂请求Miata时,car_creator_map
的miata元素将指向NULL,程序将退出。
有什么特别的私人静态积分数据成员,他们的初始化将以某种方式跳过?静态数据成员是否仅在使用类时才初始化?我的CarCreator类不在任何头文件中声明;他们完全生活在.cpp文件中。是否有可能编译器内联初始化函数,并以某种方式避免调用MyFactory :: factory_register
?
这个注册问题有更好的解决方案吗?
它不是列出IALL的CarCreators在一个单一的功能,与工厂直接注册各一台,然后以保证函数被调用的选项。特别是,我想将几个库链接在一起,并在这些单独的库中定义CarCreators,但仍然使用单个工厂来构建它们。
...
这里有一些回应,我期待,但它并没有解决我的问题:
1)你的单身厂是不是线程安全的。 a)应该不重要,我只用一个线程工作。
2)你的单身厂可能当你CarCreators被初始化未被初始化(即你有一个静态初始化失败) a)通过将单例实例放入一个函数中,我使用了单例类的安全版本。如果这是一个问题,我应该看到输出,如果我添加打印语句MiataCreator's::register_with_factory
方法:我不知道。
我认为你有一个静态初始化顺序的悲剧,但与工厂。
这并不是说它的注册标志没有被初始化,它只是不够快就被初始化。
你不能依赖静态初始化顺序除非该程度:在同一个转换单元中定义(.cpp文件)
- 静态变量将列在定义
- 静态变量的顺序初始化在该翻译单元中的任何功能或方法被首次调用之前,翻译单元将被初始化。
什么你不能依靠的是一个静态变量将一些其他翻译单元调用首次在一个函数或方法之前被初始化。
特别是,在第一次调用MyFactory :: create_car(在MyFactory.cpp中定义)之前,不能依赖MiataCreator :: registered(在Miata.cpp中定义)进行初始化。像所有未定义的行为一样,有时候你会得到你想要的东西,有时你不会,最奇怪的最不看似不相关的东西(例如静态链接和动态链接)会改变它是否以你想要的方式工作是否。
您需要做的是为Miata.cpp中定义的注册标志创建静态访问方法,并让MyFactory工厂通过此访问器获取值。由于访问器与变量定义位于同一个翻译单元中,因此该变量将在访问器运行时初始化。然后您需要从某处调用该访问器。
因此,静态数据成员并不总是在加载时初始化。有什么是吗? 我想避免让MyFactory知道它需要与哪些类进行通信。在我开始实施CarCreators之前,我只在MyFactory中有一个巨大的switch语句: switch(car_type){case} miata:return new Miata; 这个设置的一个大问题是所有的Car类都必须在工厂的同一个库中定义。随着图书馆变大,开发时间减慢。 – Andrew 2009-08-19 16:50:56
“2.在翻译单元中定义的静态变量将在该翻译单元中的任何函数或方法第一次被调用之前被初始化。”有一个鸡和鸡蛋的问题:翻译单位需要调用的知识存在于翻译单元中。那么,如果没有在集中的地方(例如Factory.cpp中)对该指令进行硬编码,就没有办法沟通“嘿,使用这些代码”?这非常令人沮丧。 – Andrew 2009-08-19 17:09:18
多数民众赞成在理论,但即使提振球员依靠静态初始化之前主http://www.boost.org/doc/libs/1_39_0/libs/flyweight/doc/tutorial/technical.html – 2009-08-19 17:58:21
如果使用静态链接,则意味着将所有对象文件(.o)添加到二进制文件,这应该像动态文件一样工作,如果制作了(.a)静态库,链接器不会将它们链接到内部在静态库内使用的对象是链接的,在这种情况下没有明确使用。
所有自动注册技术都依赖于加载时代码及其避免静态失败的方式,例如创建对象并按需返回的函数。
但是,如果你不能设法加载永远不会工作,链接目标文件一起工作,并加载动态库,但静态库永远不会链接没有明确的依赖关系。
你什么时候检查miata元素是否在地图内?它在主要之前还是之后?
我能想到的唯一原因是访问main()之前的地图元素(例如在全局初始化中),这可能发生在MiataCreator :: registered创建之前(如果它位于不同的翻译单元中)
我只在main()开始后才从地图上读取。 – Andrew 2009-08-19 16:51:54
通常对于静态库,链接器只会从主程序引用的库中提取.o文件。由于您没有参考MiataCreator :: registered或者Miata.cpp中的任何内容,但依赖于静态启动,如果链接器从静态库链接,则甚至不会将该代码包含在exe中 -
检查生成的可执行文件与nm或objdump(或如果你在Windows上的dumpbin)MiataCreator :: registered的代码是否实际包含在exe文件中,当你静态链接时。
我不知道如何强制链接到每一个位,虽然静态库的作品包括..
是的,那是我在想什么。 – 2009-08-19 21:51:10
这只是解释的一半。链接器的作用就像一个垃圾回收器:它探索所有指针(函数调用),从一些活动的(main)开始。如果在探索活动内存(从main()可能到达的函数)期间永远不会到达对象(编译单元永远不会到达),那么垃圾回收器会删除它(链接器不包含它)。 (我会在我的下一个评论中继续这个) – Andrew 2009-08-20 13:50:15
向链接器发信号通知Miata.cpp应该包含的唯一方法是拥有一些中心函数(例如,位于MyFactroy.cpp中的函数)包含对某段Miata.cpp中的数据我的观点是,这个额外的步骤与原始问题一样严格:MyFactory(或中央人员)必须知道所有CarCreators。没有解决方案,MyFactory可以等待,直到加载时间为未知的CarCreators来注册请求。我无法将CarCreators放入其他库中。 – Andrew 2009-08-20 13:51:48
就我个人而言,我认为你正在休息链接器的犯规。
布尔变量没有被使用'bool MiataCreator :: registered'os连接器没有将它们从lib中拉到可执行文件中(记住,如果在可执行文件中没有引用函数/全局变量,链接器将不会从lib中取出它们的对象[它仅查找当前在可执行文件中未定义的对象])
您可以在'bool MiataCreator :: register_with_factory()'中添加一些打印语句以查看它是否被调用。或者检查可执行文件中的符号以确认它在那里。
有些事情,我会做:
// Return the factory by reference rather than pointer.
// If you return by pointer the user has to assume it could be NULL
// Also the way you were creating the factory the destructor was never
// being called (though not probably a big deal here) so there was no
// cleanup, which may be usefull in the future. And its just neater.
MyFactory& MyFactory::get_instance()
{
static MyFactory instance; /// Safe singleton
return instance;
}
不是有对象的两步初始化。我怀疑由于链接器而失败。创建工厂的一个实例并获取构造函数进行注册。
bool MiataCreator::register_with_factory()
{
MyFactory::get_instance()->factory_register(new MiataCreator);
return true;
}
//
// I would hope that the linker does not optimize this out (but you should check).
// But the linker only pulls from (or searches in) static libraries
// for references that are explicitly not defined.
bool MiataCreator::registered(MiataCreator::register_with_factory());
我这样做:
MiataCreator::MiataCreator()
{
// I would change factory_register to take a reference.
// Though I would store it internall as a pointer in a vector.
MyFactory::getInstance().factory_register(*this);
}
// In Cpp file.
static MiataCreator factory;
链接器知道C++对象和构造函数,应该把所有的全局变量的构造有可能有副作用的影响(我知道你的布尔做的好,但我可以看到一些链接器优化出来)。
无论如何,这是我的2c值得。
使用gcc,您可以添加-Wl,--whole-archive myLib.a --Wl,--no-whole-archive
。这将强制链接器包含即使不被引用的对象。但是,这不是便携式的。
另请参阅此并发问题http://stackoverflow.com/questions/1300778/how-to-prevent-the-linker-from-optimizing-away-startup-code – 2009-08-19 15:55:40
我会使用objdump和/或nm(或Windows上的dumpbin)在最终的可执行文件上。有可能代码甚至没有包含,因为链接器看到你的程序没有引用静态库中的任何东西。 – nos 2009-08-19 16:01:55
这与静态或动态链接以及静态成员初始化*顺序未确定的事实有很大关系。泰勒麦克亨利的回答是对的。 – quark 2009-08-19 16:35:47