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

如何解决C++脚本引擎基类中泛型createClass方法无法重写的问题?

这个问题简直戳中了C++多态与模板结合的痛点——虚函数没法模板化,但你又需要不同脚本引擎子类对特定类型做定制化的类绑定逻辑。我之前做脚本绑定框架时也遇到过几乎一模一样的问题,下面给你几个比类型擦除高效得多的解决方案:

方案1:非模板虚接口 + 子类模板实现

核心思路是把模板逻辑从虚函数中剥离,基类提供一个非模板的虚接口,子类用模板方法实现具体的类型绑定逻辑,既保留动态多态的灵活性,又能利用模板的类型安全。

步骤1:定义通用的脚本类包装基类

首先创建一个非模板的抽象基类,作为所有脚本绑定类的公共接口:

class ScriptClassWrapper {
public:
    virtual ~ScriptClassWrapper() = default;
    // 通用的方法绑定接口(可根据需求扩展)
    virtual void bindMethod(const std::string& name, void* methodPtr, const std::type_info& methodType) = 0;
};

步骤2:修改ScriptEng基类

把原来的模板createClass拆分为对外的模板包装函数和子类需要实现的非模板虚函数:

#include <typeinfo>

class ScriptEng {
public:
    ScriptEng() = default;
    virtual ~ScriptEng() = default;

    // 对外友好的模板接口,方便调用者使用
    template<class T>
    ScriptClass<T>* createClass(const std::string& name, T* instance) {
        auto wrapper = createClassImpl(typeid(T), name, static_cast<void*>(instance));
        // 安全转换为具体的ScriptClass<T>,子类必须保证返回对应类型的实例
        return static_cast<ScriptClass<T>*>(wrapper);
    }

    virtual bool callFunction(const std::string& name) {
        return true;
    }

protected:
    // 子类需要实现的非模板虚接口,传递类型信息和无类型指针
    virtual ScriptClassWrapper* createClassImpl(const std::type_info& typeInfo, 
                                               const std::string& name, 
                                               void* instance) = 0;
};

步骤3:在LuaScriptEng中实现定制逻辑

子类通过模板方法处理不同类型的绑定,还可以对特定类型做特化优化:

class LuaScriptEng : public ScriptEng {
protected:
    ScriptClassWrapper* createClassImpl(const std::type_info& typeInfo, 
                                       const std::string& name, 
                                       void* instance) override {
        // 转发到模板实现函数
        return createClassTemplate(typeInfo, name, instance);
    }

private:
    // 通用模板实现,处理大部分类型的Lua绑定
    template<class T>
    LuaScriptClass<T>* createClassTemplate(const std::type_info&, const std::string& name, void* instance) {
        T* typedInstance = static_cast<T*>(instance);
        // 这里写Lua专属的绑定逻辑:创建userdata、注册元表、设置垃圾回收回调等
        auto luaClass = new LuaScriptClass<T>(name, typedInstance);
        return luaClass;
    }

    // 对Game类做特化,提前绑定通用方法或做Lua专属优化
    template<>
    LuaScriptClass<Game>* createClassTemplate<Game>(const std::type_info&, const std::string& name, void* instance) {
        Game* game = static_cast<Game*>(instance);
        auto luaGameClass = new LuaScriptClass<Game>(name, game);
        // 提前绑定hello方法,避免调用者重复写
        luaGameClass->bindMethod("hello", &Game::hello);
        return luaGameClass;
    }
};

这个方案的性能开销极小:只有一次虚函数调用,后续的模板逻辑都是编译时确定的,完全没有类型擦除的多层调用开销。

方案2:动态注册类型工厂函数

如果不想修改基类的虚函数结构,可以用类型映射表来注册不同类型的创建函数,灵活性更高,还能支持动态添加类型。

修改ScriptEng基类

添加一个类型到创建函数的映射表,对外提供注册接口和模板化的createClass:

#include <typeindex>
#include <unordered_map>
#include <functional>

class ScriptEng {
public:
    using ClassCreateFunc = std::function<ScriptClassWrapper*(const std::string&, void*)>;

    ScriptEng() = default;
    virtual ~ScriptEng() = default;

    // 注册特定类型的创建函数
    template<class T>
    void registerClassCreator(ClassCreateFunc func) {
        _creators[type_index(typeid(T))] = func;
    }

    // 对外的模板调用接口
    template<class T>
    ScriptClass<T>* createClass(const std::string& name, T* instance) {
        auto it = _creators.find(type_index(typeid(T)));
        if (it != _creators.end()) {
            auto wrapper = it->second(name, static_cast<void*>(instance));
            return static_cast<ScriptClass<T>*>(wrapper);
        }
        // 未注册时返回默认实现,或抛出异常
        return new ScriptClass<T>(instance);
    }

    virtual bool callFunction(const std::string& name) {
        return true;
    }

private:
    std::unordered_map<std::type_index, ClassCreateFunc> _creators;
};

在LuaScriptEng中注册创建函数

在子类构造函数中注册各个类型的Lua绑定逻辑:

class LuaScriptEng : public ScriptEng {
public:
    LuaScriptEng() {
        // 注册Game类的Lua绑定函数
        registerClassCreator<Game>([](const std::string& name, void* instance) {
            Game* game = static_cast<Game*>(instance);
            auto luaClass = new LuaScriptClass<Game>(name, game);
            luaClass->bindMethod("hello", &Game::hello);
            return luaClass;
        });

        // 注册其他类型...
        registerClassCreator<Player>([](const std::string& name, void* instance) {
            return new LuaScriptClass<Player>(name, static_cast<Player*>(instance));
        });
    }
};

这个方案的性能开销主要是一次哈希表查找(unordered_map的查找时间接近O(1)),然后是一次函数调用,比类型擦除高效得多,而且不需要修改基类的虚函数结构,扩展性更强。

方案对比

  • 方案1更适合类型固定、需要在子类中集中处理绑定逻辑的场景,代码结构更清晰,性能略优。
  • 方案2更适合需要动态添加类型、或者不想修改基类虚函数的场景,灵活性更高。

两种方案都能完美支持你想要的脚本调用形式(比如Game.somefunction()),因为子类的LuaScriptClass<T>可以实现对应的脚本语言绑定逻辑(比如Lua中把方法注册到元表,让脚本可以用面向对象的方式调用)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:33:43