You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Factory注册表宏放置在头文件的类定义之前?

问题背景

当前项目中的ENGINE_SPAWNABLE宏用于将GameObject子类注册到工厂注册表,但必须放在类定义之后:

#pragma once

#include "GameObject.h"

class Foo : public GameObject
{
//...
};

ENGINE_SPAWNABLE(Foo);

这种写法在大型头文件或包含多个类的头文件中,宏的位置难以快速定位。希望将宏调整到类定义之前,写法如下:

#pragma once

#include "GameObject.h"

ENGINE_SPAWNABLE();
class Foo : public GameObject
{
//...
};

但现有宏的实现依赖类的完整定义才能识别类名,需要重新设计宏和注册逻辑。

现有核心代码如下:

#pragma once

#include <functional>
#include <memory>
#include <string>
#include <unordered_map>

#include "Object.h"

// 原宏定义
#define ENGINE_SPAWNABLE(CLASSNAME) static bool CLASSNAME##Registered \
= (GameObjectFactory::GetInstance().GetGameObjectRegistry()[#CLASSNAME] = &GameObjectFactory::SpawnObject<CLASSNAME>, true)

class GameObjectFactory
{
public:
    static GameObjectFactory& GetInstance()
    {
        static GameObjectFactory Instance;
        return Instance;
    }

    template <typename TObject>
    static std::unique_ptr<Object> SpawnObject()
    {
        return std::make_unique<TObject>();
    }

    std::shared_ptr<Object> SpawnGameObjectByName(const std::string& Name);
    std::unordered_map<std::string, std::function<std::shared_ptr<Object>()>>& GetGameObjectRegistry();

private:
    std::unordered_map<std::string, std::function<std::unique_ptr<Object>()>> Registry;
};

解决方案

方案一:利用静态成员延迟注册(轻量改造)

通过前置声明辅助模板类,将注册逻辑延迟到类定义完成后执行,让宏可以放在类定义之前。

1. 修改宏与添加辅助模板

// 前置声明辅助模板,用于延迟触发注册
template<typename T>
struct SpawnableRegistrar;

// 调整后的宏,放在类定义之前
#define ENGINE_SPAWNABLE(CLASSNAME) \
    class CLASSNAME; \
    template<> struct SpawnableRegistrar<CLASSNAME>; \
    friend struct SpawnableRegistrar<CLASSNAME>;

// 辅助模板实现,负责实际注册逻辑
template<typename T>
struct SpawnableRegistrar {
    static bool Registered;
};

template<typename T>
bool SpawnableRegistrar<T>::Registered = 
    (GameObjectFactory::GetInstance().GetGameObjectRegistry()[#T] = 
     []() -> std::shared_ptr<Object> { return std::make_shared<T>(); }, true);

2. 类定义中添加触发成员

在类内部添加静态成员,触发辅助模板的初始化:

#pragma once

#include "GameObject.h"

ENGINE_SPAWNABLE(Foo);
class Foo : public GameObject
{
private:
    static const bool _spawnableRegistration;
};

// 在头文件末尾或对应cpp文件中定义该成员(确保类已完整定义)
const bool Foo::_spawnableRegistration = SpawnableRegistrar<Foo>::Registered;

方案二:宏自动注入注册逻辑(简化写法)

进一步封装宏,让它自动完成类的前置声明、注册触发成员的注入,减少手动代码:

// 辅助模板保持不变
template<typename T>
struct SpawnableRegistrar {
    static bool Registered;
};

template<typename T>
bool SpawnableRegistrar<T>::Registered = 
    (GameObjectFactory::GetInstance().GetGameObjectRegistry()[#T] = 
     []() -> std::shared_ptr<Object> { return std::make_shared<T>(); }, true);

// 前置宏:放在类定义之前
#define ENGINE_SPAWNABLE(CLASSNAME) \
    class CLASSNAME; \
    template<> struct SpawnableRegistrar<CLASSNAME>; \
    friend struct SpawnableRegistrar<CLASSNAME>; \
    \
    class CLASSNAME : public GameObject \
    { \
    private: \
        static const bool _spawnableRegistration; \
    public:

// 后置宏:放在类定义末尾
#define ENGINE_SPAWNABLE_END \
    }; \
    const bool CLASSNAME::_spawnableRegistration = SpawnableRegistrar<CLASSNAME>::Registered;

使用示例

#pragma once

#include "GameObject.h"

ENGINE_SPAWNABLE(Foo)
    // 类的成员定义
    void Update() override {};
ENGINE_SPAWNABLE_END

关键注意事项
  1. 类型匹配修正:原工厂类中Registry的函数类型是std::function<std::unique_ptr<Object>()>,但SpawnGameObjectByName返回std::shared_ptr,存在类型不匹配。需统一类型,建议将SpawnObject改为返回std::shared_ptr:
    template <typename TObject>
    static std::shared_ptr<Object> SpawnObject()
    {
        return std::make_shared<TObject>();
    }
    // 同步修改注册表类型
    std::unordered_map<std::string, std::function<std::shared_ptr<Object>()>> Registry;
    
  2. 初始化顺序:依赖GameObjectFactory的懒加载单例特性(当前实现已满足),确保注册表在注册逻辑执行前已初始化。
  3. 类名字符串可靠性:如果使用typeid(T).name()作为注册表键,不同编译器返回的字符串格式可能不一致;显式使用#CLASSNAME(宏字符串化)更可靠。

内容的提问来源于stack exchange,提问作者user21681526

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 11:17:47