为什么静态数据成员可能无法初始化?
我试图在加载时向工厂注册一堆类。 我的策略是利用静态初始化来确保在 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::registered 将被初始化,静态链接我的库,它不会被初始化。
使用静态构建,当有人去工厂请求 Miata 时,car_creator_map
的 miata 元素将指向 NULL,并且程序将退出。
私有静态整型数据成员是否有什么特殊之处,它们的初始化将以某种方式被跳过? 静态数据成员是否仅在使用类时才初始化? 我的 CarCreator 类未在任何头文件中声明; 它们完全位于 .cpp 文件中。 编译器是否有可能内联初始化函数并以某种方式避免调用 MyFactory::factory_register
?
有没有更好的办法解决这个注册问题?
在单个函数中列出所有 CarCreators、向工厂显式注册每个 CarCreators、然后保证调用该函数并不是一种选择。 特别是,我想将几个库链接在一起并在这些单独的库中定义 CarCreators,但仍然使用单个工厂来构建它们。
...
以下是我期待的一些答复,但它们不能解决我的问题:
1)您的单例工厂不是线程安全的。 a)没关系,我只使用一个线程。
2) 当你的 CarCreators 初始化时,你的单例工厂可能未初始化(即你遇到了静态初始化失败) a)我通过将单例实例放入函数中来使用单例类的安全版本。 如果这是一个问题,如果我向 MiataCreator's::register_with_factory 方法添加打印语句,我应该会看到输出:我没有。
I'm trying to register a bunch of classes with a factory at load time. My strategy is to harness static initialization to make sure that before main() begins, the factory is ready to go. This strategy seems to work when I link my library dynamically, but not when I link statically; when I link statically, only some of my static data members get initialized.
Let's say my factory builds Cars. I have CarCreator classes that can instantiate a handful of cars, but not all. I want the factory to collect all of these CarCreator classes so that code looking for a new Car can go to the factory without having to know who will be doing the actual construction.
So I've got
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 );
}
...
Then I'll have specific cars and specific car creators:
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() );
...
To reiterate: dynamically linking my libraries, MiataCreator::registered will get initialized, statically linking my libraries, it will not get initialized.
With a static build, when someone goes to the factory to request a Miata, the miata element of the car_creator_map
will point to NULL and the program will exit.
Is there anything special with private static integral data members that their initialization will be somehow skipped? Are static data members only initialized if the class is used? My CarCreator classes are not declared in any header file; they live entirely within the .cpp file. Is it possible that the compiler is inlining the initialization function and somehow avoiding the call to MyFactory::factory_register
?
Is there a better solution to this registration problem?
It is not an option to list iall of the CarCreators in a single function, register each one explicitly with the factory, and then to guarantee that the function is called. In particular, I want to link several libraries together and define CarCreators in these separate libraries, but still use a singular factory to construct them.
...
Here are some responses I am anticipating but which do not address my problem:
1) your singleton Factory isn't thread safe.
a) Shouldn't matter, I'm working with only a single thread.
2) your singleton Factory may be uninitialized when your CarCreators are being initialized (i.e. you've got a static initialization fiasco)
a) I'm using a safe version of the singleton class by putting the singleton instance into a function. If this were a problem, I should see output if I added a print statement to the MiataCreator's::register_with_factory
method: I don't.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
我认为你有一个静态初始化顺序惨败,但不是与工厂。
这并不是说注册标志没有被初始化,而是没有足够快地初始化。
不能依赖静态初始化顺序,除非:
您不能依赖的是,在第一次调用其他翻译单元中的函数或方法之前,静态变量将被初始化。
特别是,在首次调用 MyFactory::create_car(在 MyFactory.cpp 中定义)之前,您不能依赖 MiataCreator::registered(在 Miata.cpp 中定义)进行初始化。
像所有未定义的行为一样,有时您会得到您想要的东西,有时您不会,并且最奇怪的最看似无关的事情(例如静态链接与动态链接)可以改变它是否按照您想要的方式工作。
您需要做的是为 Miata.cpp 中定义的注册标志创建静态访问器方法,并让 MyFactory 工厂通过此访问器获取值。 由于访问器与变量定义位于同一翻译单元中,因此变量将在访问器运行时初始化。 然后您需要从某个地方调用此访问器。
I think you have a static initialization order fiasco, but not with the Factory.
It's not that it's the registered flag is not getting initialized, it's just not getting initialized soon enough.
You cannot rely on static initialization order except to the extent that:
What you cannot rely on is that a static variable will be initialized before a function or method in some other translation unit is invoked for the first time.
In particular, you cannot rely on MiataCreator::registered (defined in Miata.cpp) to be initialized before MyFactory::create_car (defined in MyFactory.cpp) is invoked for the first time.
Like all undefined behavior, sometimes you will get what you want, and sometimes you won't, and the strangest most seemingly-unrelated things (such as static versus dynamic linking) can change whether it works the way you want it to or not.
What you need to do is create static accessor method for the registered flag that is defined in Miata.cpp, and have the MyFactory factory get the value through this accessor. Since the accessor is in the same translation unit as the variable definition, the variable will be initialized by the time the accessor runs. You then need to call this accessor from somewhere.
如果使用静态链接,您的意思是将所有目标文件(.o)添加到二进制文件中,那么它应该像动态文件一样工作,如果您创建了(.a)静态库,则链接器不会将它们链接到内部,因为只有在静态库是链接的,在这种情况下没有显式使用。
所有自动注册技术都依赖于加载时代码及其避免静态失败的方法,例如创建对象并根据需要返回它的函数。
但是,如果您无法加载它,它将无法工作,将目标文件链接在一起可以工作,并且也可以加载动态库,但静态库永远不会在没有显式依赖项的情况下链接。
If with static linking you mean adding all object files(.o) to the binary, that should work like the dynamic stuff, if you made a (.a) static library the linker will not link them inside as only the used objects inside the static library are linked and in this case none is used explicitly.
All the auto-registering techinques depend on load-time code and its ways to avoid the static fiasco, like the function that creates the object and returns it on demand.
But if you don't manage to load that ever it won't work, linking object files together works, and loading dynamic libraries as well, but static libraries will never link without explicit dependencies.
通常,对于静态库,链接器只会从主程序引用的库中提取 .o 文件。 由于您没有引用 MiataCreator::registered 或 Miata.cpp 中的任何内容,而是依赖于静态初始化,因此如果链接器是从静态库链接的,则链接器甚至不会在您的 exe 中包含该代码 -
使用 nm 或 objdump 检查生成的可执行文件(如果您在 Windows 上,则为 dumpbin)当您静态链接时,MiataCreator::registered 的代码是否实际包含在 exe 文件中。
我不知道如何强制链接器包含静态库的每个部分。
Generally with static libaries, the linker will only pull out the .o files from that library which the main program references. Since you're not referencing MiataCreator::registered or really anything in Miata.cpp but rely on static initiailization the linker won't even include that code in your exe if it's linked from a static library-
Check the resulting executable with nm or objdump(or dumpbin if you are on windows) whether the code for MiataCreator::registered is actually included in the exe file when you link statically.
I don't know how to force the linker to include every bits and pieces of a static library though..
我个人认为你对链接器犯规了。
布尔变量未使用“bool MiataCreator::registered”操作系统,链接器不会将它们从库中提取到可执行文件中(请记住,如果可执行文件中没有对函数/全局的引用,则链接器不会从中提取对象lib [它只查找当前在可执行文件中未定义的对象])
您可以在“bool MiataCreator::register_with_factory()”中添加一些打印语句以查看它是否被调用。 或者检查可执行文件中的符号以验证它是否存在。
我会做的一些事情:
而不是对对象进行两步初始化。 我怀疑由于链接器而失败。 创建工厂的实例并获取构造函数来注册它。
我会这样做:
链接器了解 C++ 对象和构造函数,并且应该提取所有全局变量,因为构造函数可能会产生副作用(我知道你的 bool 也这样做,但我可以看到一些链接器对此进行了优化)。
不管怎样,这就是我2c的价值。
Personally I think you are fallowing foul of the linker.
The boolean variables are not being used 'bool MiataCreator::registered' os the linker is not pulling them from the lib into the executable (remember if there is no reference to a function/global in the executable the linker will not pull them object from the lib [It only looks for objects that are currently undefined in the executable])
You could add some print statements in 'bool MiataCreator::register_with_factory()' to see if it is ever being called. Or check the symbols in your executable to verify that it is there.
Some things I would do:
Rather than have a two step initialisation of the object. Which I suspec is failing because of the linker. Create an instance of your factory and get the constructor to register it.
I would do this:
The linker knows about C++ objects and constructors and should pull all global variables as the constructors can potentially have side affects (I know your bool does as well, but I can see some linkers optimizing that out).
Anyway thats my 2c worth.
使用 gcc,您可以添加
-Wl,--whole-archive myLib.a --Wl,--no-whole-archive
。 这将强制链接器包含对象,即使未引用也是如此。 然而,这是不可移植的。With gcc, you can add
-Wl,--whole-archive myLib.a --Wl,--no-whole-archive
. This will force the linker to include the objects even if not refered to. This is, however, not portable.什么时候检查 miata 元素是否在地图内? 是在 main 之前还是之后?
我能想到的唯一原因是在 main() 之前访问地图元素(例如在全局初始化中),这可能在创建 MiataCreator::registered 之前发生(如果它位于不同的翻译单元中)
When do you check if the miata element is inside the map? is it before or after main?
The only reason I could think of is accessing the map elements before main() (such as in global initialization), which may take place before the creation of MiataCreator::registered (if it's in a different translation unit)