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

如何创建成员函数指针映射并在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 17:07:51