如何创建成员函数指针映射并在C++命令行Shell中调用
问题描述
为应用构建命令行Shell时,识别到映射命令无法正确调用成员函数,精简示例代码如下:
#include <iostream> #include <string> #include <vector> #include <map> #include <functional> typedef std::vector<std::string> token_list ; class CommandShell { public: CommandShell() {} void runShell(std::ostream& out, std::istream& in); void cmdSave(std::ostream& out, token_list); void cmdLoad(std::ostream& out, token_list); private: //--- important application config values stored here and modified // by user commands in the shell }; void CommandShell::cmdSave(std::ostream& out, token_list) { out << "we're in the save function\n"; } void CommandShell::cmdLoad(std::ostream& out, token_list) { out << "we're in the load function\n"; } void CommandShell::runShell(std::ostream& out, std::istream& in) { //--- create the command/function map typedef void (CommandShell::*userCommand)(std::ostream&, std::vector<std::string>&); std::map<std::string, userCommand> cmd_map; cmd_map.emplace("save", std::mem_fn(&CommandShell::cmdSave)); cmd_map.emplace("load", std::mem_fn(&CommandShell::cmdLoad)); token_list command_and_options; // created by parsing the whole input string std::string cmd_line = ""; while (true) { in >> cmd_line; // tokenize the line here -- simplified for this example command_and_options.push_back(cmd_line); command_and_options.push_back("option1"); if (command_and_options[0] == "quit") { break; } else { auto this_command = cmd_map.find(command_and_options[0]); if (this_command == cmd_map.end()) { std::cout << "unrecognized command!\n"; } else { //--- COMPILATION ERRORS here // I've tried this... std::invoke((*this_command).second, this, std::cout, command_and_options); // and this... std::invoke(this_command->second, this, std::cout, command_and_options); // and this... // (which actually works if I don't use member functions in the map) (*this_command).second(std::cout, command_and_options); } } } } int main() { CommandShell my_shell; my_shell.runShell(std::cout, std::cin); }
错误原因与修正方案
1. 核心错误点
- 类型不匹配:
userCommand是原始成员函数指针类型,但std::mem_fn返回的是可调用包装器,两者无法直接存入同一个map。 - 参数签名不一致:成员函数的第二个参数是值传递的
token_list,但userCommand定义的是引用传递的std::vector<std::string>&,签名冲突。 - 调用逻辑错误:原始成员函数指针需要通过对象实例调用,直接调用或错误使用
std::invoke都会导致编译失败。
2. 修正后的完整代码
#include <iostream> #include <string> #include <vector> #include <map> #include <functional> typedef std::vector<std::string> token_list ; class CommandShell { public: CommandShell() {} void runShell(std::ostream& out, std::istream& in); void cmdSave(std::ostream& out, token_list); void cmdLoad(std::ostream& out, token_list); private: //--- important application config values stored here and modified // by user commands in the shell }; void CommandShell::cmdSave(std::ostream& out, token_list) { out << "we're in the save function\n"; } void CommandShell::cmdLoad(std::ostream& out, token_list) { out << "we're in the load function\n"; } void CommandShell::runShell(std::ostream& out, std::istream& in) { // 使用std::function统一包装成员函数,签名与实际成员函数保持一致 std::map<std::string, std::function<void(std::ostream&, token_list)>> cmd_map; // 绑定当前对象this和成员函数,生成可调用对象 cmd_map["save"] = std::bind(&CommandShell::cmdSave, this, std::placeholders::_1, std::placeholders::_2); cmd_map["load"] = std::bind(&CommandShell::cmdLoad, this, std::placeholders::_1, std::placeholders::_2); token_list command_and_options; std::string cmd_line = ""; while (true) { in >> cmd_line; command_and_options.clear(); // 每次循环清空,避免累积旧数据 command_and_options.push_back(cmd_line); command_and_options.push_back("option1"); if (command_and_options[0] == "quit") { break; } else { auto iter = cmd_map.find(command_and_options[0]); if (iter == cmd_map.end()) { std::cout << "unrecognized command!\n"; } else { // 直接调用包装后的函数,参数与签名匹配 iter->second(out, command_and_options); } } } } int main() { CommandShell my_shell; my_shell.runShell(std::cout, std::cin); }
3. 原理说明
std::function的作用:作为通用的可调用对象包装器,统一管理不同类型的可调用对象(成员函数、普通函数、lambda等),解决类型不兼容问题。std::bind的作用:将成员函数与对象实例this绑定,把成员函数转化为可以直接调用的函数对象,同时通过std::placeholders保留参数位置,确保调用时传递正确的参数。- 参数一致性:确保map中
std::function的签名与成员函数完全一致,避免因值/引用传递差异导致的编译错误。 - 额外优化:每次循环清空
command_and_options,避免旧数据累积影响后续命令处理。
内容的提问来源于stack exchange,提问作者PeterT
相关产品推荐
相关产品推荐

