2

在挖掘网络之后,我发现了一些关于利用 CRTP 允许在运行时实例化静态成员的强大模式的参考:

C++:编译未使用的类

其他类的初始化类 - 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 回答 3

4

I can describe the cause of the problem; unfortunately I can't offer a solution.

The problem is that initialisation of a variable with static storage duration may be deferred until any time before the first use of something defined in the same translation unit. If your program never uses anything in the same translation unit as CCRTPAction<CDummyAction>::m_bForceRegistration, then that variable may never be initialised.

As you found, including the header in the translation unit that defines main will force it to be initialised at some point before the start of main; but of course that solution won't meet your first requirement. My usual solution to the problems of initialising static data across multiple translation units is to avoid static data altogether (and the Singleton anti-pattern doubly so, although that's the least of your problems here).

于 2013-07-29T11:35:15.697 回答
1

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

一些框架可以轻松加载自定义插件,例如 Qt

于 2013-07-29T12:35:47.917 回答
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调用列表,为应用程序中的每个模块生成一个调用列表,并输出生成的头文件:

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)
于 2013-07-29T12:19:30.423 回答