如何为文本式命令控制台存储带强类型参数的函数?
问题描述
应用场景
我正尝试为文本式命令控制台构建一个小型解释器。例如,假设有如下函数:
void SetBrightness(float brightness) { /* ... */ }
用户应能在控制台输入"SetBrightness 0.5"来调用上述函数。我们可在运行时注册命令:
void AddCommand(const std::string& command, CommandInterpreterCallback* callback);
该函数需将回调函数及其匹配的强类型参数列表绑定到command字符串。我定义了如下类来存储回调:
using ParamType = std::variant<int32_t, float, std::string, bool>; class CommandInterpreterCallback { public: void SetParameters(const std::vector<ParamType>& args) { mNumParameters = args.size(); if (mNumParameters >= 1) { mArg1 = args[0]; } // ... } void SetFunction(std::function<void> callback) { mCallback = callback; // TODO: How to solve this? } private: uint8_t mNumParameters = 0; ParamType mArg1 = ParamType<bool>; // possibly an array instead here std::function<void> mCallback; // TODO: How to solve this? };
面临挑战
我希望参数传递是强类型的,例如SetBrightness()必须接收浮点数,若用户输入"SetBrightness true"则不应调用该函数。同时我不想将函数参数指定为std::variant<...>,因为这会使代码杂乱,且在其他代码中调用该函数更困难。
但我不确定如何在类中声明mCallback成员,因为它需要支持参数化类型。我知道可以将其类型设为std::function<void(float)>,但这样又无法绑定接收布尔值的其他函数。
或许可以用std::variant来指定mCallback的类型,但这似乎会带来指数级复杂度的问题。
有没有好的方法规避这些限制?
解决方案
核心思路:用多态实现类型擦除
放弃将所有回调类型塞进std::variant的方案,改用抽象基类+模板派生类的方式实现类型擦除,既支持任意参数类型的函数,又能保留强类型校验能力,同时避免类型组合爆炸。
具体实现
1. 定义抽象回调基类
using ParamType = std::variant<int32_t, float, std::string, bool>; class BaseCommandCallback { public: virtual ~BaseCommandCallback() = default; // 接收解析后的参数,执行类型校验并调用回调 virtual bool Execute(const std::vector<ParamType>& args) = 0; // 获取期望的参数类型列表,用于提前校验输入 virtual const std::vector<std::type_index>& GetParamTypes() const = 0; };
2. 实现模板化的具体回调类
这个类会自动推导目标函数的参数类型,处理参数转换和校验逻辑:
template<typename... Args> class TypedCommandCallback : public BaseCommandCallback { public: explicit TypedCommandCallback(std::function<void(Args...)> func) : m_func(std::move(func)) { // 初始化期望的参数类型列表 (m_paramTypes.push_back(typeid(Args)), ...); } bool Execute(const std::vector<ParamType>& args) override { // 先检查参数数量是否匹配 if (args.size() != sizeof...(Args)) return false; // 尝试将每个variant转换为对应类型,失败则返回false try { ExecuteImpl(args, std::index_sequence_for<Args...>{}); return true; } catch (const std::bad_variant_access&) { return false; } } const std::vector<std::type_index>& GetParamTypes() const override { return m_paramTypes; } private: template<std::size_t... Idx> void ExecuteImpl(const std::vector<ParamType>& args, std::index_sequence<Idx...>) { m_func(std::get<Args>(args[Idx])...); } std::function<void(Args...)> m_func; std::vector<std::type_index> m_paramTypes; };
3. 改造CommandInterpreterCallback
现在它只需要持有基类指针,无需自己存储参数和回调:
class CommandInterpreterCallback { public: template<typename Func> void SetFunction(Func&& func) { // 推导函数参数类型,创建对应TypedCommandCallback实例 using FuncWrapper = decltype(std::function{std::forward<Func>(func)}); using CallbackType = TypedCommandCallback<typename FuncWrapper::argument_type...>; m_callback = std::make_unique<CallbackType>(std::forward<Func>(func)); } bool Execute(const std::vector<ParamType>& args) { return m_callback ? m_callback->Execute(args) : false; } const std::vector<std::type_index>& GetParamTypes() const { static const std::vector<std::type_index> empty; return m_callback ? m_callback->GetParamTypes() : empty; } private: std::unique_ptr<BaseCommandCallback> m_callback; };
4. 使用示例
// 注册命令 void RegisterCommands() { CommandInterpreterCallback brightnessCb; brightnessCb.SetFunction(&SetBrightness); AddCommand("SetBrightness", &brightnessCb); // 注册带bool参数的命令 CommandInterpreterCallback enabledCb; enabledCb.SetFunction([](bool enabled) { /* 执行逻辑 */ }); AddCommand("SetEnabled", &enabledCb); } // 处理用户输入 void HandleUserInput(const std::string& input) { // 拆分命令名和参数,解析为std::vector<ParamType> std::string cmd = ParseCommandName(input); std::vector<ParamType> args = ParseInputArgs(input); if (auto* cb = FindCommand(cmd)) { if (!cb->Execute(args)) { std::cout << "参数类型或数量错误:" << cmd << std::endl; } } }
方案优势
- 强类型校验:自动检查参数数量和类型,类型不匹配时直接拒绝执行
- 原函数无侵入:无需修改原有业务函数的参数类型,依然可以正常直接调用
- 扩展性强:新增参数类型只需扩展
ParamType的variant定义,无需修改回调框架 - 避免类型爆炸:用模板+多态替代所有
std::function组合的variant存储,代码复杂度可控
内容的提问来源于stack exchange,提问作者alexpanter
相关产品推荐
相关产品推荐

