在单独的库中使用CRTP进行静态成员初始化

时间:2013-07-29 10:09:01

标签: c++ templates design-patterns

在挖掘网页后,我发现了一些强大的模式的引用,该模式利用CRTP来允许在静态成员的运行时进行实例化:

C++: Compiling unused classes

Initialization class for other classes - C++

等等。 除非将类层次结构放入外部库,否则所提出的方法很有效。 这样做,运行时初始化不再起作用,除非我手动#include派生类的头文件。但是,这违背了我的主要目的 - 更改为我的应用程序添加新命令而无需更改其他源文件。

一些代码,希望它有所帮助:

class CAction
{
protected:
    // some non relevant stuff
public:
    // some other public API
    CAction(void) {}
    virtual ~CAction(void) {}

    virtual std::wstring Name() const = 0;
};

template <class TAction>
class CCRTPAction : public CAction
{
public:
    static bool m_bForceRegistration;
    CCRTPAction(void) { m_bForceRegistration; }
    ~CCRTPAction(void) { }

    static bool init() {
        CActionManager::Instance()->Add(std::shared_ptr<CAction>(new TAction));
        return true;
    }
};

template<class TAction> bool CCRTPAction<TAction>::m_bForceRegistration = CCRTPAction<TAction>::init();

以这种方式完成实施:

class CDummyAction : public CCRTPAction<CDummyAction>
{
public:
    CDummyAction() { }
    ~CDummyAction() { }
    std::wstring Name() const { return L"Dummy"; }
};

最后,这是容器类API:

class CActionManager
{
private:
    CActionManager(void);
    ~CActionManager(void);
    std::vector<std::shared_ptr<CAction>> m_vActions;   
    static CActionManager* instance;
public:
    void Add(std::shared_ptr<CAction>& Action);
    const std::vector<std::shared_ptr<CAction>>& AvailableActions() const;
    static CActionManager* Instance() {
        if (nullptr == instance) {
            instance = new CActionManager();
        }
        return instance;
    }
};

在单个项目解决方案中,一切正常。但是,如果我将上面的代码放在一个单独的.lib中,那么魔法会以某种方式中断并且实现类(DummyAction等等)不再被实例化。

我在某个地方看到#include "DummyAction.h",无论是在我的图书馆还是在主要项目中都可以让事情发挥作用,但是

  1. 对于我们的项目,必须添加操作不需要更改其他文件。
  2. 我真的不明白幕后发生的事情,这让我很不舒服。我真的很讨厌取决于我不能完全掌握的解决方案,因为在将软件发送给客户之前的任何时间,可能是一天,错误都可能出现:)
  3. 更奇怪的是,放置#include指令但不在头文件中定义构造函数/析构函数仍然打破了魔力。
  4. 感谢大家的关注。我真的希望有人能够解决一些问题......

3 个答案:

答案 0 :(得分:3)

我可以描述问题的原因;不幸的是,我无法提供解决方案。

问题是,具有静态存储持续时间的变量的初始化可能会推迟到第一次使用同一翻译单元中定义的某些内容之前的任何时间。如果您的程序从未使用与CCRTPAction<CDummyAction>::m_bForceRegistration相同的翻译单元中的任何内容,那么该变量可能永远不会被初始化。

正如您所发现的那样,在翻译单元中包含定义main的标题会强制它在main开始之前的某个时刻进行初始化;但当然,该解决方案无法满足您的第一个要求。我通常的解决方案是在多个翻译单元中初始化静态数据的问题是完全避免静态数据(而Singleton反模式是双重的,尽管这是你问题中最少的。)

答案 1 :(得分:1)

如Mike的回答所述,编译器确定从不使用静态成员CCRTPAction<CDummyAction>::m_bForceRegistration,因此不需要初始化。

您尝试解决的问题是初始化一组“插件”模块,而不必将#include代码放在中心位置。 CTRP和模板对您没有帮助。我不知道C ++中的(可移植)方式生成代码来初始化一组未从main()引用的插件模块。

如果您愿意(必须)在中心位置(不包括其标题)列出插件模块(合理)让步,那么就有一个简单的解决方案。我相信这是函数范围extern声明有用的极少数情况之一。你可能会认为这是一个肮脏的黑客,但是当没有别的办法时,一个肮脏的黑客成为一个优雅的解决方案;)。

此代码编译为主可执行文件:

core/module.h

template<void (*init)()>
struct Module
{
    Module()
    {
        init();
    }
};

// generates: extern void initDummy(); Module<initDummy> DummyInstance
#define MODULE_INSTANCE(name) \
    extern void init ## name(); \
    Module<init ## name> name ## Instance

core/action.h

struct Action // an abstract action
{
};

void addAction(Action& action); // adds the abstract action to a list

main.cpp

#include "core/module.h"

int main()
{
    MODULE_INSTANCE(Dummy);
}

此代码实现Dummy模块并编译为单独的库:

dummy/action.h

#include "core/action.h"

struct DummyAction : Action // a concrete action
{
};

dummy/init.cpp

#include "action.h"

void initDummy()
{
    addAction(*new DummyAction());
}

如果你想更进一步(这部分不可移植)你可以编写一个单独的程序来生成MODULE_INSTANCE调用列表,一个用于应用程序中的每个模块,并输出生成的头文件:< / p>

generated/init.h

#include "core/module.h"

#define MODULE_INSTANCES \
    MODULE_INSTANCE(Module1); \
    MODULE_INSTANCE(Module2); \
    MODULE_INSTANCE(Module3);

将其添加为预构建步骤,core/main.cpp变为:

#include "generated/init.h"

int main()
{
    MODULE_INSTANCES
}

如果您以后决定动态加载部分或全部这些模块,则可以使用完全相同的模式来动态加载,初始化和卸载dll。请注意,以下示例是特定于Windows的,未经测试且不处理错误:

core/dynamicmodule.h

struct DynamicModule
{
    HMODULE dll;

    DynamicModule(const char* filename, const char* init)
    {
        dll = LoadLibrary(filename);
        FARPROC function = GetProcAddress(dll, init);
        function();
    }
    ~DynamicModule()
    {
        FreeLibrary(dll);
    }
};

#define DYNAMICMODULE_INSTANCE(name) \
    DynamicModule name ## Instance = DynamicModule(#name ".dll", "init" #name)

答案 2 :(得分:1)

正如Mike Seymour所说,静态模板内容不会为您提供所需的动态加载工具。您可以作为插件动态加载模块。将包含操作的dll分别放入应用程序的工作目录中,并在运行时动态加载这些dll。这样,您就不必更改源代码,以便使用CAction的不同或新实现。

一些框架可以轻松加载自定义插件for example Qt