6

我正在尝试在加载时向工厂注册一堆类。我的策略是利用静态初始化来确保在 main() 开始之前,工厂已准备就绪。当我动态链接我的库时,这种策略似乎有效,但当我静态链接时无效;当我静态链接时,只有我的一些静态数据成员被初始化。

假设我的工厂生产汽车。我有 CarCreator 类可以实例化少量汽车,但不是全部。我希望工厂收集所有这些 CarCreator 类,以便寻找新车的代码可以进入工厂,而无需知道谁将进行实际构建。

所以我有

CarTypes.hpp

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

我的工厂.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;
};

我的工厂.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 时,miata 元素car_creator_map将指向 NULL,程序将退出。

私有静态整数数据成员有什么特别之处,它们的初始化会以某种方式被跳过吗?是否仅在使用类时才初始化静态数据成员?我的 CarCreator 类没有在任何头文件中声明;它们完全存在于 .cpp 文件中。编译器是否有可能内联初始化函数并以某种方式避免调用 MyFactory:: factory_register

这个注册问题有更好的解决方案吗?

在单个函数中列出所有 CarCreators 并在工厂中显式注册每个 CarCreators 并确保调用该函数不是一种选择。特别是,我想将几​​个库链接在一起并在这些单独的库中定义 CarCreators,但仍然使用单个工厂来构造它们。

...

以下是我期待的一些回应,但不能解决我的问题:

1)你的单身工厂不是线程安全的。a) 没关系,我只使用一个线程。

2) 当您的 CarCreators 正在初始化时,您的单例工厂可能未初始化(即您有一个静态初始化失败) a) 我通过将单例实例放入函数中来使用单例类的安全版本。如果这是一个问题,如果我在方法中添加了一个打印语句,我应该会看到输出MiataCreator's::register_with_factory:我没有。

4

6 回答 6

8

我认为你有一个静态初始化命令惨败,但不是与工厂。

并不是注册标志没有被初始化,它只是没有足够快地被初始化。

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

  1. 在同一翻译单元(.cpp 文件)中定义的静态变量将按列出的顺序初始化
  2. 翻译单元中定义的静态变量将在该翻译单元中的任何函数或方法第一次被调用之前被初始化。

不能依赖的是静态变量将在第一次调用其他翻译单元中的函数或方法之前被初始化。

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

像所有未定义的行为一样,有时你会得到你想要的,有时你不会,最奇怪的最看似不相关的事情(例如静态与动态链接)可能会改变它是否按照你想要的方式工作。

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

于 2009-08-19T16:22:59.927 回答
3

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

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

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

于 2009-08-19T15:53:21.423 回答
1

通常对于静态库,链接器只会从主程序引用的库中提取 .o 文件。由于您没有引用 MiataCreator::registered 或 Miata.cpp 中的任何内容,而是依赖于静态初始化,因此如果它是从静态库链接的,则链接器甚至不会在您的 exe 中包含该代码-

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

我不知道如何强制链接器包含静态库的每一个点点滴滴。

于 2009-08-19T16:22:56.667 回答
1

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

于 2010-01-30T16:46:46.463 回答
0

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

于 2009-08-19T16:11:19.000 回答
0

就我个人而言,我认为您正在对链接器犯规。

布尔变量没有被使用 '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++ 对象和构造函数,并且应该提取所有全局变量,因为构造函数可能会产生副作用(我知道您的 bool 也是如此,但我可以看到一些链接器对其进行了优化)。

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

于 2009-08-19T21:48:23.407 回答