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

时间:2009-08-19 15:42:18

标签: c++ initialization static-members

我正在尝试在加载时向工厂注册一堆类。我的策略是利用静态初始化来确保在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)当你的CarCreator被初始化时你的单件工厂可能未被初始化(即你有一个静态初始化惨败) a)我通过将单例实例放入函数中来使用单例类的安全版本。如果这是一个问题,如果我在MiataCreator's::register_with_factory方法中添加了一个print语句,我应该看到输出:我没有。

6 个答案:

答案 0 :(得分:8)

我认为你有一个静态初始化命令fiasco,但没有Factory。

并不是因为注册的标志没有被初始化,所以它不会很快被初始化。

您不能依赖静态初始化顺序,除非:

  1. 在同一翻译单元(.cpp文件)中定义的静态变量将按列出的顺序初始化
  2. 翻译单元中定义的静态变量将在首次调用该翻译单元中的任何函数或方法之前进行初始化。
  3. 无法依赖的是静态变量将在第一次调用某些其他翻译单元中的函数或方法之前初始化。

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

    与所有未定义的行为一样,有时候你会得到你想要的东西,有时你也不会,而最奇怪的看似无关的东西(如静态与动态链接)可以改变它是否以你想要的方式运行或不。

    您需要做的是为Miata.cpp中定义的已注册标志创建静态访问器方法,并让MyFactory工厂通过此访问器获取值。由于访问器与变量定义位于相同的转换单元中,因此变量将在访问器运行时初始化。然后,您需要从某个地方调用此访问者。

答案 1 :(得分:3)

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

所有自动注册技术都依赖于加载时代码及其避免静态惨败的方法,例如创建对象并按需返回的函数。

但是如果你没有设法加载,它将无法工作,链接目标文件一起工作,并加载动态库,但静态库永远不会链接没有明确的依赖。

答案 2 :(得分:1)

通常使用静态库,链接器只会从主程序引用的库中提取.o文件。既然你没有引用MiataCreator ::注册或Miata.cpp中的任何东西,但是依赖于静态初始化,如果链接是从静态库链接,链接器甚至不会在你的exe中包含该代码 -

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

我不知道如何强制链接器包含静态库的每个部分......

答案 3 :(得分:1)

使用gcc,您可以添加-Wl,--whole-archive myLib.a --Wl,--no-whole-archive。即使没有引用,这也会强制链接器包含对象。但是,这不是便携式的。

答案 4 :(得分:0)

您何时检查miata元素是否在地图内?是在主要之前还是之后? 我能想到的唯一原因是在main()之前访问map元素(例如在全局初始化中),这可能发生在创建MiataCreator :: registered之前(如果它在不同的翻译单元中)

答案 5 :(得分:0)

就我个人而言,我认为你正在愚弄链接器。

没有使用布尔变量'bool MiataCreator :: registered'os链接器没有将它们从lib中拉到可执行文件中(请记住,如果在可执行文件中没有对函数/ global的引用,链接器将不会它们来自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 ++对象和构造函数,并且应该提取所有全局变量,因为构造函数可能会产生副作用(我知道你的bool也是如此,但我可以看到一些链接器优化了它)。

无论如何这就是我的2c价值。

相关问题