简体   繁体   English

为什么静态数据成员可能无法初始化?

[英]Why might a static data member not get initialized?

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. 我的策略是利用静态初始化来确保在main()开始之前,工厂已准备就绪。 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. 我有CarCreator类可以实例化一些汽车,但不是全部。 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. 我希望工厂收集所有这些CarCreator类,以便寻找新车的代码可以去工厂而不必知道谁将进行实际构造。

So I've got 所以我有

CarTypes.hpp CarTypes.hpp

enum CarTypes
{
   prius = 0,
   miata,
   hooptie,
   n_car_types
};

MyFactory.hpp 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.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 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. 重申:动态链接我的库,MiataCreator :: registered将被初始化,静态链接我的库,它将不会被初始化。

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. 随着静电积累,当有人去工厂请求马自达Miata的的Miata的元素car_creator_map将指向NULL和程序将退出。

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; 我的CarCreator类未在任何头文件中声明; they live entirely within the .cpp file. 它们完全位于.cpp文件中。 Is it possible that the compiler is inlining the initialization function and somehow avoiding the call to MyFactory:: factory_register ? 有没有可能是编译器内联初始化函数并以某种方式避免调用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. 不能在单个函数中列出所有CarCreators,使用工厂明确注册每个CarCreators,然后保证调用该函数。 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. 特别是,我想将几​​个库链接在一起并在这些单独的库中定义CarCreators,但仍然使用单个工厂来构造它们。

... ...

Here are some responses I am anticipating but which do not address my problem: 以下是我期待的一些回复,但没有解决我的问题:

1) your singleton Factory isn't thread safe. 1)您的单件工厂不是线程安全的。 a) Shouldn't matter, I'm working with only a single thread. a)无所谓,我只使用一个线程。

2) your singleton Factory may be uninitialized when your CarCreators are being initialized (ie 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. 2)当你的CarCreator被初始化时你的单件工厂可能没有初始化(即你有一个静态初始化惨败)a)我通过将单例实例放入一个函数来使用单例类的安全版本。 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. 如果这是一个问题,如果我在MiataCreator's::register_with_factory方法中添加了一个print语句,我应该看到输出:我没有。

I think you have a static initialization order fiasco, but not with the Factory. 我认为你有一个静态初始化命令fiasco,但不是与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: 您不能依赖静态初始化顺序,除非:

  1. Static variables defined in the same translation unit (.cpp file) will be initialized in the order listed 在同一翻译单元(.cpp文件)中定义的静态变量将按列出的顺序初始化
  2. Static variables defined in a translation unit will be initialized before any function or method in that translation unit is invoked for the first time. 在第一次调用该翻译单元中的任何函数或方法之前,将初始化翻译单元中定义的静态变量。

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. 特别是,在第一次调用MyFactory :: create_car(在MyFactory.cpp中定义)之前,不能依赖MiataCreator :: registered(在Miata.cpp中定义)进行初始化。

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. 您需要做的是为Miata.cpp中定义的已注册标志创建静态访问器方法,并让MyFactory工厂通过此访问器获取值。 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. 然后,您需要从某个地方调用此访问器。

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. 如果使用静态链接,则意味着将所有目标文件(.o)添加到二进制文件中,这应该像动态内容一样工作,如果您创建了一个(.a)静态库,链接器将不会将它们链接到内部,因为只有内部使用的对象静态库是链接的,在这种情况下,没有显式使用。

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. 但是,如果您无法加载它将无法正常工作,将目标文件链接在一起工作,并加载动态库,但静态库永远不会在没有显式依赖项的情况下进行链接。

Generally with static libaries, the linker will only pull out the .o files from that library which the main program references. 通常使用静态库,链接器只会从主程序引用的库中提取.o文件。 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- 由于你没有引用MiataCreator ::注册或Miata.cpp中的任何内容,但依赖于静态初始化,如果链接从静态库链接,链接器甚至不会在你的exe中包含该代码 -

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. 使用nm或objdump检查生成的可执行文件(如果在Windows上,则检查dumpbin)是否静态链接时,MiataCreator :: registered的代码是否实际包含在exe文件中。

I don't know how to force the linker to include every bits and pieces of a static library though.. 我不知道如何强制链接器包含静态库的每个部分。

With gcc, you can add -Wl,--whole-archive myLib.a --Wl,--no-whole-archive . 使用gcc,您可以添加-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. 然而,这不是便携式的。

When do you check if the miata element is inside the map? 你什么时候检查miata元素是否在地图内? 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) 我能想到的唯一原因是在main()之前访问map元素(例如在全局初始化中),这可能发生在创建MiataCreator :: registered之前(如果它在不同的翻译单元中)

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]) 布尔变量没有被使用'bool MiataCreator :: registered'os链接器没有将它们从lib拉到可执行文件中(请记住,如果在可执行文件中没有对函数/ global的引用,链接器将不会从它们拉出它们lib [它只查找可执行文件中当前未定义的对象])

You could add some print statements in 'bool MiataCreator::register_with_factory()' to see if it is ever being called. 您可以在'bool MiataCreator :: register_with_factory()'中添加一些打印语句,以查看它是否被调用。 Or check the symbols in your executable to verify that it is there. 或者检查可执行文件中的符号以验证它是否存在。

Some things I would do: 我会做的一些事情:

// 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;
}

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. 创建工厂的实例并获取构造函数以进行注册。

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() );

I would do this: 我会这样做:

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;

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). 链接器知道C ++对象和构造函数,并且应该提取所有全局变量,因为构造函数可能有副作用(我知道你的bool也有,但我可以看到一些链接器优化了它)。

Anyway thats my 2c worth. 无论如何,这是我的2c价值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM