V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
fyyz
V2EX  ›  C

C++怎么构建 key 是 string, value 是函数的 map

  •  
  •   fyyz · 2018-02-20 16:17:29 +08:00 · 5100 次点击
    这是一个创建于 2469 天前的主题,其中的信息可能已经有所发展或是发生改变。

    C++怎么构建 key 是 string,value 是函数的 map

    网上查了下,用 std::function 可以表示一个函数对象,目前我写出来的类型是这样的:

    std::map<
        std::string, 
        std::function<
            std::string(param_1&, param_1&)
        >
    >
    handle_map;
    

    这样写对吗?

    还有,这种 map 应该怎么 insert 数据进去啊,insert 的时候 std::function 对象是要用 std::bind 构造出来丢进去吗?

    47 条回复    2018-02-22 11:49:45 +08:00
    iceheart
        1
    iceheart  
       2018-02-20 16:33:12 +08:00 via Android   ❤️ 1
    param_1 和 param_2 换成参数类型
    handle_map["hello"] = [] (class1 &x, class2 &y) {return "world";}
    snnn
        2
    snnn  
       2018-02-20 16:35:03 +08:00 via Android
    你会用函数指针吗?如果简单的东西就能搞定,就别太复杂
    monnand
        3
    monnand  
       2018-02-20 16:53:11 +08:00 via Android
    @iceheart 说得对。

    千万别按 @snnn 说的用函数指针。现在已经 2018 年了
    laike9m
        4
    laike9m  
       2018-02-20 18:45:42 +08:00 via Android
    @snnn 没看出 std::function 哪里复杂
    fyyz
        5
    fyyz  
    OP
       2018-02-20 18:53:00 +08:00
    @iceheart 我要回调的函数挺大的,如果全写成 lambad 会很乱,用 bind 可以吗
    geelaw
        6
    geelaw  
       2018-02-20 18:57:25 +08:00
    @fyyz 你送进去的只要是能接受 param_1 &, param_2 & 形参,返回值能隐式转换为 std::string 的仿函数即可。
    snnn
        8
    snnn  
       2018-02-20 20:13:42 +08:00 via Android   ❤️ 2
    我汗。。。
    你们都是群天才
    bramblex
        9
    bramblex  
       2018-02-20 20:26:57 +08:00
    @snnn

    std::function 怎么也比函数指针简单吧,怎么就天才了?

    以及,好久不见呀 /w|
    q397064399
        10
    q397064399  
       2018-02-20 20:35:26 +08:00
    C++有这么复杂吗?
    你把 key 弄成 string value 弄成 type + param1 + param2 组成的结构体 根据 type 调用不同的函数不就好了吗?
    inkedawn
        11
    inkedawn  
       2018-02-20 20:45:16 +08:00
    > std::function 的实例能存储、复制及调用任何可调用 (Callable) 目标——函数、lambda 表达式、bind 表达式或其他函数对象,还有指向成员函数指针和指向数据成员指针。
    http://zh.cppreference.com/w/cpp/utility/functional/function
    inkedawn
        12
    inkedawn  
       2018-02-20 20:54:22 +08:00
    std::bind 是用来封装绑定参数的,如果不需要对函数包装的话直接 insert 就可以了,通过 std::function 的构造函数可以直接隐式转换。

    std::string f(param_1&, param_1&);
    handle_map["hello"] = f;// f 隐式转换为 std::function
    wizardforcel
        13
    wizardforcel  
       2018-02-20 21:06:41 +08:00
    @q397064399 lambda 实际上就是 operator() ,但是你定义结构体的时间我都把 lambda 写好了。
    q397064399
        14
    q397064399  
       2018-02-20 21:10:30 +08:00
    @wizardforcel #13 我是觉得蠢办法好使,也容易改,C++就是各种特性 搞来搞去 把语言给搞复杂了
    bramblex
        15
    bramblex  
       2018-02-20 21:14:15 +08:00
    @q397064399

    但是有好用的特性干嘛不用,楼上说的好呀,都 2018 年了,c 艹 11 都不用上吗?
    ipwx
        16
    ipwx  
       2018-02-20 21:28:30 +08:00
    @snnn 函数指针不能绑定局部产生的参数,这个很麻烦。还是 std::function 好使。

    不过 @fyyz 像函数对象这种可能会有很大的上下文的对象,我觉得还是用 shared_ptr 更好,不然会产生对闭包的多次拷贝。如果闭包还有文件对象什么的,不是要跪了。
    gnaggnoyil
        17
    gnaggnoyil  
       2018-02-20 21:59:20 +08:00
    @geelaw 讲道理"形参"没有"接受"这一说吧.严格的说法应该是"参数列表能接受(param_1 左值,param_2 左值)"大概?
    geelaw
        18
    geelaw  
       2018-02-20 22:18:02 +08:00
    @gnaggnoyil 应该是:能接受 param_1 &, param_2 & 的实参。
    gnaggnoyil
        19
    gnaggnoyil  
       2018-02-20 22:21:26 +08:00
    @geelaw 实参本身作为表达式其类型不能是引用类型的,对,就算是由指向某个引用的单个名字构成的 id-expression 也不能.
    sinxccc
        20
    sinxccc  
       2018-02-20 23:13:54 +08:00
    C++ 的设计哲学和有点本来就是可以让人根据自己的需要灵活选择特性,而不是被语言绑架啊。

    用函数指针还是 std::function 显然都行,这有什么可争的。
    geelaw
        21
    geelaw  
       2018-02-20 23:26:42 +08:00
    @gnaggnoyil 我并没说过那是“类型”,而且并不需要第一个参数的类型是 param_1 且类别是左值,考虑第三个类型 T,若 T 有一个可以可以被访问的 operator param_1 & (),也是可以被接受的实参。
    inflationaaron
        22
    inflationaaron  
       2018-02-20 23:26:54 +08:00
    @sinxccc 都 8102 年了还抱着 C89 不放,C++ 标准委员会要哭了。没有直接去掉特性是历史遗留问题,激进的语言早就废除了,只不过没有办法一刀切,但这不代表函数指针就是现代 C++ 的做法。我们要鼓励使用更好的解决方案。
    inkedawn
        23
    inkedawn  
       2018-02-21 00:43:58 +08:00
    新东西固然有学习成本,但是学完确实用着方便啊。
    所谓的复杂,只是由于新东西有学习成本,又有已掌握的方案(虽然用起来麻烦些),而不想去了解新东西而已
    TaoSama
        24
    TaoSama  
       2018-02-21 02:10:13 +08:00 via Android   ❤️ 1
    看那些楼上的。。都 8012 年了+1。。
    还在用 C99 甚至 C89。。赶紧学学 cpp11 和 cpp14 吧。
    毕竟 cpp17 都这么久了 马上 cpp19 就来了。。
    zmj1316
        25
    zmj1316  
       2018-02-21 08:27:52 +08:00 via Android
    @TaoSama 下一个是 c++ 20 ......
    linux40
        26
    linux40  
       2018-02-21 09:23:55 +08:00 via Android
    comparator 难道不是必须
    bool comp(const std::string &, const std::string &)
    么,你传的啥比较器啊。
    wwqgtxx
        27
    wwqgtxx  
       2018-02-21 09:36:01 +08:00
    @linux40 人家传的是 value 的类型,不是 comparator 的
    linux40
        28
    linux40  
       2018-02-21 09:36:49 +08:00 via Android
    @wwqgtxx 对哈,眼花了。。。
    gnaggnoyil
        29
    gnaggnoyil  
       2018-02-21 09:57:12 +08:00
    @geelaw 喷了……我竟然忘了这茬……好吧我现在算是明白标准里为啥动不动就出现"overload resolution"这个词了.这个词确实可以避免踩坑 233
    coordinate
        30
    coordinate  
       2018-02-21 10:08:18 +08:00
    因为有的人用的编译器还是 vc6.0,所有他们不能接受 c++2.0,而且这类人还很多
    RaynorGu
        31
    RaynorGu  
       2018-02-21 10:35:17 +08:00
    函数传个 lambda 就好了
    liuminghao233
        32
    liuminghao233  
       2018-02-21 11:12:57 +08:00   ❤️ 1
    随手写了一个

    ```
    #include <boost/unordered_map.hpp>
    #include <boost/function.hpp>
    #include <string>

    void print666()
    {
    printf("666\n");
    }

    template<class T>
    using UMAP = boost::unordered_map<std::string, boost::function<T()>>;

    int main()
    {
    UMAP<void> umap;

    umap.insert(UMAP<void>::value_type("666", boost::bind(print666)));

    auto res = umap.find("666");

    if (res != umap.end())
    {
    res->second();
    }
    }

    ```

    这样写的话 value 只能是同款的 Function
    TaoSama
        33
    TaoSama  
       2018-02-21 11:37:26 +08:00 via Android
    @zmj1316 尴尬。。下一个标准这么远啊 垃圾语言毁我青春
    p64381
        34
    p64381  
       2018-02-21 12:01:19 +08:00
    我们石器时代的人这么写

    typedef void (*CALL_BACK)(void*);
    struct callback{
    struct rb_node rb; // 用红黑树查找
    // or
    struct list_head lh; // 链表顺序查找
    CALL_BACK cb;
    char name[];
    };

    再或者用 hash 存 struct callback*, 然后 hash 查找
    或者直接把 struct callback* 按照 name 排序存入数组, 然后二分查找 (本质和 rbtree 差不多)。

    石器时代的人就这么几个简单粗暴的招式,使起来干净简单粗暴,出货也很快。
    wizardforcel
        35
    wizardforcel  
       2018-02-21 12:31:21 +08:00
    @sinxccc 因为现代 C++ 不提倡 raw array 和 raw ptr。这也是为推动 C++ 发展做贡献。
    bookit
        36
    bookit  
       2018-02-21 14:00:18 +08:00
    不如用 C 来写,杜绝 C++
    skadi
        37
    skadi  
       2018-02-21 14:39:34 +08:00
    用 14 以上标准. lambda 封装.

    std::map<string,function<void(void)>>mp;
    mp["fuck"]=[/*some& or some=*/](){
    //do something
    }

    或者你需要返回值,统一封装成 std::future

    std::map<std::string, std::function<void(void)>>mp;

    template<typename F,typename ...Args>
    auto insert(std::string&& key,F&&f,Args&&...args)noexcept {
    using r = std::invoke_result_t<F, Args...>;
    auto task_pack_ptr = std::make_shared<std::packaged_task<r()>>([&]() -> r {
    return std::invoke(std::forward<F>(f), std::forward<Args>(args)...);
    });
    auto result = task_pack_ptr->get_future();
    mp[std::forward<std::string>(key)] = [task_pack_ptr]() {
    (*task_pack_ptr);
    };
    return result;
    }
    ipwx
        38
    ipwx  
       2018-02-21 14:40:28 +08:00   ❤️ 2
    @p64381 其实事情是这样的。如果只把函数指针替换成 std::function,当然没有比函数指针简单多少。std::function 和 lambda 表达式是一整套体系,会影响到你顶层的设计。你想想,如果你要把某个函数指针加上一些参数当做一个整体塞到 map 里面当 value,你用石器时代的招式是不是先要搞一整套铺垫?用上 std::function 和 lambda,通过闭包捕获局部参数,铺垫就都不需要了。你可以用更简洁的设计去做同样的事情。

    如果你用过 scala (估计你没有用过),那你也许对 akka 这种 actor framework 有非常好的印象。没用过 actor,也许你用过 scala 的 future。又或者你没用过 scala,但是用过 nodejs,那你可能对回调也有印象。这三种新时代的并发程序设计方法,比线程池不知道高效到哪里去了,都强烈依赖局部闭包。所以 C++ 新特性对于新时代的并发程序还是很有帮助的。有一个库叫做 C++ Actor Framework,就走在路上。
    skadi
        39
    skadi  
       2018-02-21 15:01:27 +08:00
    撒币了... 应该是 (*task_pack_ptr)(); 才对...(逃
    snnn
        40
    snnn  
       2018-02-21 15:02:18 +08:00
    @bramblex
    函数指针,是个很底层的东西。不管是什么操作系统,什么编译器,你可以很清楚的知道它编译成机器码是什么样子。而 std::function,是 C++标准库的一部分,不同的平台有不同的实现,而且调用栈巨复杂。
    函数指针你按指传递就可以了,而 std::function 你还得考虑到底是传值,还是传引用,还是 move。函数指针你可以大胆的往 STL 容器里面放,怎么整都行。而 std::function 你就得三思了,搞不好取出来的就是空的了。

    “ A callable type is a pointer to function, a pointer to member function, a pointer to member data, or a class type whose objects can appear immediately to the left of a function call operator.”
    std::function 是对以上种种的统一封装,它的优势是以上种种都可以转成 std::function,这样传参的时候更通用。缺点是我们自己写代码的时候往往不需要这样过度的封装。我又不是要写一个 generic library 给所有人用,我干嘛要考虑那么多不同的情况。
    gnaggnoyil
        41
    gnaggnoyil  
       2018-02-21 16:49:48 +08:00
    @snnn 我不太清楚贵司业务注重哪块,不过对于我而言,函数指针不能放闭包,光这一点在很多时候就足以否决它了.就连 Scheme 都有 call/cc 呢,就别假装和 Haskell 多亲近了.至于说额外用个 struct 包起来什么的……都封装到这种地步了还不如 std::function 一把梭.当然确实 C++的整个闭包体系面对多线程环境对象的生命周期问题确实有点挫,但是无脑 std::shared_ptr 目前也将就够用,更何况函数指针面对这种情况只能使问题更加混乱——用裸指针管理好多线程对象生命周期的问题谁敢说是"简单"的?反正我认为这至少不比搞清楚右值引用和移动语义更简单——而且大不了无脑完美转发,撑死多写几个重载.又不是要写 extern "C"的接口,搞的那么简陋干嘛……

    而且 C++一来不一定是编译的,比如 Jypiter notebook ——是的没错这货就在最近加入了对 C/C++的支持;二来就算是编译的也不一定是编译到机器码,比如 emsrcipten.js;三来就算是编译到机器码也不保证源码和目标机器码有直接对应关系,只要符合 as-if rule 编译器心情好直接就给你内联了都说不定:GCC 老早就能对虚函数调用进行内联分析了,函数指针不可能就完全两眼一抹黑;四来不同平台的机器码显然又不一样.基于以上理由"观察编译后的目标代码来分析源码"最多只能算是特定情况下的辅助源码分析手段,而大部分情况下要照着语言的语义和库的文档进行分析(当然 C 和 C++在这里一坨坨的未定义行为对这种分析方法造成了非常大的阻碍,不过良好的编码习惯还是能一定程度上减轻其影响).而论及库的语义的话 std::function 并不比函数指针复杂多少——甚至它作为一个类型擦除的容器要比泛泛而谈的仿函对象要更简单.就算是编译错误,错误还能长到哪里去?反正比那些模板满天飞的库(比如 boost.spirit, boost.spirit, boost.spirit 和 boost.spirit)的编译错误短多了,用多了也就熟了.

    C++确实在功能强大程度和易用性等多方面上有很多欠缺,甚至在可见的将来很多欠缺难以有解决的可能.不过除非特殊情况特殊考虑,否则我不认为回滚到 C 上是个更好的选择.
    p64381
        42
    p64381  
       2018-02-21 17:05:40 +08:00
    @ipwx 不用铺垫什么东西啊,把结构体里面加个成员就行了啊

    typedef void (*CALL_BACK)(void*);
    struct callback{
    CALL_BACK cb;
    void *cb_param;
    char name[];
    };

    然后这样写
    struct callback *p = xxxxx;
    p->cb(p->cb_param);

    或者干脆一些
    p->cb(p);
    ipwx
        43
    ipwx  
       2018-02-21 17:32:05 +08:00   ❤️ 1
    @p64381 其一,如果你用结构体,需要 malloc。那什么时候析构?在复杂的并发程序(比如 actor framework 里面),你必须依赖内存托管,因为这个函数的生命周期是框架负责的,而不是你程序员负责的。用 std::function,那么只要你绑定给它的参数有正确的垃圾回收和拷贝机制(比如正确的拷贝构造,或者智能指针比如 std::shared_ptr ),那么 std::function 就会遵循这套机制。

    其二,std::function 可以借助 bind 在普通函数上绑定临时参数,比如从一个 (int,int,std::string) 特化一个 (int,int)。当然你可以说,我用结构体存参数啊?这不对,因为我的接受者只接受 (int,int),连结构体都不接受,你哪里传进去呢?你只能特例化一个 (int,int) 函数,想办法把 std::string 和这个特例化参数绑定…… 说实话我不太清楚该怎么用 C 做这件事情,除非你说服那个接受者重新写一个接受 (int,int,struct Context*) 的一套东西出来。

    更进一步地,std::function 和 std::bind 的价值在于给所有 C++ 的用户,类库的撰写者、类库的使用者,提供了一个统一的抽象接口。它解耦了这两个角色,类库设计者只要关注 (int,int),而类库使用者不受 (int,int) 这个参数签名的限制。不要有附加的 struct,不许要处理垃圾回收。只要符合 std::function 这套接口的标准,管你是函数指针,是函数对象( struct + operator()),是对象的某个成员函数,是类的静态函数,还是通过 std::bind 诱导出的 partial function,又或是局部的 lambda,反正对于接受者都一样没区别。

    新时代的 C++ 适合大型工程,乃至于开源世界各种风格迥异的类库,这是 C++ 这套的价值所在。这些都是石器时代的那套不能解决的问题。
    - - - -

    C 那套不是完全不好,比如 ABI 统一,一个二进制库永流传。C++ 这套在这方面是有缺陷的,C++ 的 ABI 被人长期诟病,std::function 跨编译器使用可能会出现各种问题。但是这套在开源世界里面其实不成问题,你完全可以用统一的编译器进行编译。反而是 C 那套,没有一套统一的兼容层解耦调用者和类库设计者,所以必须自己处理所有事情。类库就很难写了。

    我只能说到这里了。我知道你说的 struct 那套,我也用过 C,而且好几年前还沉迷过,觉得 C++ 太臃肿,编译出来的程序太大,编译太慢,还是 C 好。但是后来用过 c#、用过 python、回头好好学了学 c++(真是太多东西了,编译器方面比如模板元编程,enable_if,再比如各种 boost 库),又用过 scala。用过这么多东西,我只能说 C 真的表达力比其他所有语言弱一大截。很多设计方法你必须借助高级特性才能写得出来,而且对于 C++ 而言新特性才出来不久,可能还不好写。这会迫使你转投比如 scala akka。不过学了这么多,可选择余地也大得多,我现在写个什么玩意儿,也能就着需求挑语言了。因为不同语言写同样的东西,顶层设计可能差异巨大,而这种巨大的差异也会带来巨大的工作量的区别。

    你可能不知道我说的一些东西,包括 actor framework 之类的。如果有时间强烈推荐你去看看。目前成熟的 actor framework 我觉得非 scala akka 莫属。你学过之后绝对会在写并发程序方面有不一样的体会的,会得到弥足珍贵的思想的。
    wizardforcel
        44
    wizardforcel  
       2018-02-21 17:39:58 +08:00 via Android
    @snnn 用 c++就好好用,否则就滚去用 c,别搞得不伦不类的。
    Akiyu
        45
    Akiyu  
       2018-02-21 18:05:08 +08:00
    c++ primer 上面有,你网上也能查到相关的代码
    问之前先自己去实验实验,你问的问题试试也就知道了。
    你真的是想来问问题的么?
    看看楼上,引了一堆战
    innoink
        46
    innoink  
       2018-02-21 20:20:52 +08:00
    std::bind 不是必须,自己查 std::function 的构造函数

    其他的自行查阅:

    http://en.cppreference.com/w/cpp/container/map
    jasonlz
        47
    jasonlz  
       2018-02-22 11:49:45 +08:00
    用 lambda
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   925 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 21:12 · PVG 05:12 · LAX 13:12 · JFK 16:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.