Effective STL-6 遍布STL的 functor

part6 遍布STL的 functor, 通常应该可配接(adaptable), 并经 function Adapter 进一步配接

总结:

(1)让functor 继承自unary_function/binary_function, 从而继承了它们提供的 特殊类型定义(typedef: argument_type/result_type/...), 从而成为可配接的(adaptable)

可配接的对象 经过function Adapter(函数配接器/适配器, not1/bind2nd/...)进一步配接(如 operator() 结果取反)后, 返回的仍是可配接对象

(2) 而, 作STL算法参数时, 相对于用函数(指针), 用 函数对象 会带来 抽象性利益

细节:

(1) 应用

[1] 关联容器中用于排序

[2] STL算法(for_each/transform/find_if等)中用于 操作参数条件 (pred) 参数

(2)函数 配接器(function Adapter: not1/ bind2nd) 可动态地生成 functor

内部机制: 保存其要配接的(预测式/返回bool的)可配接对象 pred + 转调用 可配接对象 pred 的 operator() + 取 逻辑非 !

    client 对not1返回的可配接对象做 `opertor() 操作` 时, 
    会被导引`调用 其内部所含可配接对象pred的 opertor() 操作`, 
    再 `取 逻辑非 !`
    template <class Predicate> 
        unary_negate<Predicate> 
    not1 (const Predicate& pred) // 模板函数, 对可配接对象, 取否定后返回的仍是可配接对象
    {
        return unary_negate<Predicate>(pred);
    }

    template <class Predicate> 
    class unary_negate
        : public unary_function <typename Predicate::argument_type, bool>
    {
    protected:
        Predicate pred_;
    public:
        explicit 
        unary_negate (const Predicate& pred) 
        : pred_ (pred) {}

        bool 
        operator() (const typename Predicate::argument_type& x) const 
        {   return !pred_(x); }
    };

(3) not1/ptr_fun 内部机制本质相同, 不同的是, 保存的是 可配接的预测式 pred / 不可配接的 函数指针

item40: functor/函数对象通常应该可配接, 除非不用考虑与函数对象配接器(not1等)配接

可配接的函数对象能与其他STL组件更默契地协同工作, 能用于更多的 context: 少代价, 带来多便利

1 ptr_fun 的引入: 指针容器 + 查找 + 否定含义的条件pred

方法: 先对条件/predptr_fun 包装成函数对象, 再用函数配接器 not1 取否定

即, not1( ptr_fun(pred) ): 条件 pred = 函数指针 isInteresting

例: 在 widgetPtrLst 中查找第1个不满足条件 isInteresting()的指针(Widget*)

    bool isInteresting(const Widget* pw);
    {
        Widget wInterested(...);
        return lhs.getInterestValue() < wInterested.getInterestValue();
    }
    
    list<Widget*>::iterator iter =
        find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
                not1( ptr_fun(isInteresting) ) ); // isInteresting 是函数指针
            
    if(iter != widgetPtrLst.end() )
    // ...

2 ptr_fun 作用, 机制

(1) 作用: 完成最多3种特殊的类型定义(typedef), 供4个标准函数配接器(not1/not2/bind1st/bind2nd) 使用, 来实现可配接

    特殊的类型定义(typedef)
        argument_type
        first_argument_type/second_
        result_type
        的子集

(2) 机制: ptr_fun 内部

[1] 继承自 std::unary_function 或 std::binary_function, 以 继承其提供函数对象配接器所需的特殊类型定义(typedef)

指定 unary_/binary_ 的模板形参ptr_fun 的 operator()参数(去掉const+引用/不去掉const+指针 后的)类型 和 返回类型

一般规则: 指针作参数或返回类型的functor, 传给 unary_function/binary_function 的类型operator()的参数和返回类型完全相同

[2] 保存函数指针 + 其 operator() 转调用 函数指针的 operator()/call

    template <class Arg, class Result>
        pointer_to_unary_function<Arg,Result> 
    ptr_fun (Result (*pf)(Arg))
    {
        return pointer_to_unary_function<Arg,Result>(pf);
    }

    template <class Arg, class Result>
    class pointer_to_unary_function 
        : public unary_function <Arg, Result>
    {
    protected:
        Result(*pf)(Arg);
    public:
        explicit pointer_to_unary_function ( Result (*pf_)(Arg) ) 
            : pf (pf_) {}
        
        Result 
        operator() (Arg x) const
        { return pf(x); }
    };

(3) ptr_fun 实现可配接functor 的范例

    struct TNameCompare // 无状态 functor => 用 struct, 而非 class
        : public std::binary_function<T, T, bool>
    {
        bool
        operator()(const T& lhs, const T& lhs) const;
    };
    
    struct TPtrTNameCompare
        : public std::binary_function<const T*, const T*, bool>
    {
        bool
        operator()(const T* lhs, const T* rhs) const;
    };

STL中所有 无状态 functor (如 less<T> / plus<T>等)一般都被定义成 struct

(4) 函数指针 为什么不能直接用于 函数配接器 not1 等? 答: 缺少函数配接器所需要的特殊类型定义

3 functor 含多个 函数调用操作符 operator(), 则 最多只有1种调用形式 可配接: 与 unary_/binary_ 匹配的 -> 半配接能力的 functor -> 往往是设计错误

4 函数对象 / 函数指针 / 函数 的不同应用场合

(1) 否定含义的条件pred 的表达

[1] 用函数(指针): 不能直接用, 需要用ptr_fun 包装一层成为函数对象

如前

[2] 用函数对象

临时对象 Widget(...) lifetimefind_if 执行完才结束, 保证算法内部可以用 const 引用使用它

    list<Widget*>::iterator iter =
    find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
            not1( IsInteresting<Widget>(Widget(...) ) ) ); 

functor 保存的对象操作的对象 类型可以不同, 只要能基于相同类型 提供比较语义(如 operator <)

// gcc4.9 C++98 下 OK, VS2019下OK

#include <list>
#include <functional>
#include <iostream>
#include <algorithm>

using namespace std;

class Widget
{
private:
    int interestValue;
public:
    Widget(int interestValue_) : interestValue(interestValue_) {}
    Widget() : interestValue(0) {}
    int getInterestValue() const { return interestValue; }
};

template<typename T>
struct IsInteresting
    : public std::unary_function<const Widget*, bool> // 与 operator()(.) 参数和返回值类型要匹配
{
private:
    const T& tInterested; // const T& tInterested;
public:
    IsInteresting(const T& tInterested_)
        :tInterested(tInterested_) {}

    bool 
    operator()(const Widget* lhs) const // 也可以用 const T* lhs
    {
        return lhs->getInterestValue() == tInterested.getInterestValue();
    }
};

int main()
{
    list<Widget*> widgetPtrLst;
    widgetPtrLst.push_back(new Widget(10));
    widgetPtrLst.push_back(new Widget(3) );
    widgetPtrLst.push_back(new Widget(2) );

    list<Widget*>::iterator iter = 
        find_if(widgetPtrLst.begin(), widgetPtrLst.end(), 
                not1(IsInteresting<Widget>(Widget(10) ) ) );

    std::cout << (*iter)->getInterestValue();
}
print 
3

(2) 可以用函数对象, 也可以用函数指针: STL算法的操作参数

为什么可以用函数指针?靠(模板)函数模板实参推断, 可推断出操作实参(函数指针)的类型

(3) 只能用函数对象, 不能用函数(指针): 作模板形参

例: 比较函数对象作关联容器的模板形参, 指定排列顺序

    set<string, CIStringCompare> strSet; // item19

    typedef set<string*, StringPtrDereferenceLess> StrPtrSet;
    StrPtrSet strPtrSet;                 // item20

为什么不能用函数指针? (模板)类没有 模板实参推断机制

(4) 能用函数指针, 不能用函数(名): 可调用对象

为什么不能用函数? 函数不是对象/函数类型不能直接定义对象

     1] 函数类型
            
            不能直接 定义对象
            
    2] 函数引用 
    
        可视为 const 函数指针 

item41 ptr_fun/mem_fun/mem_fun_ref

1 由来: 在对象调用函数 <=> 用函数操作对象

2类3种方式, 本质相同: pObj->f() / obj.f() 最终被编译器编译为 f(pObj) f(&obj)

    (1) 面向过程: 对象作函数的参数

    (2) 面向对象: 函数作对象成员, 让对象自己去调用自己的函数
    f(obj)      // 语法1 -> ptr_fun

    pObj->f();  // 语法2 -> mem_fun

    obj.f()     // 语法3 -> mem_fun_ref

2 功能

(1) 函数模板 ptr_fun/mem_fun

使函数(指针) /成员函数(指针) 可配接

(2) mem_fun/mem_fun_ref

用来调整(通过语法2/3被调用的)成员函数, 使之能够通过语法1被调用,

从而能用于STL算法(for_each等), 因为STL算法通过语法1调用函数

    template<typename ReturnType, class C>
        mem_fun_t<ReturnType, C>
    mem_fun(ReturnType (C::*pmf)() )
    { return mem_fun_t<S,T>(pmf); }

    template <class S, class T>
    class mem_fun_t : public unary_function <T*,S>
    {
        S (T::*pmem)();
    public:
        explicit mem_fun_t ( S (T::*p)() ) 
            : pmem (p) {}
        
        S operator() (T* p) const // operator() 的参数必须是裸指针
        { 
            return (p->*pmem)(); 
        }
    };

mem_fn 的返回类型的 operator() 的参数可为任意类型, 不再像 mem_fun 那样要求为裸指针

std::mem_fn 的返回类型

未指定, 但其 operator() 的参数可为任意类型
    template<class... Args>
    /* see below */ 
    operator()(Args&&... args) /* cvref-qualifiers */
        noexcept(/* see below */);
    list<Widget*>::iterator iter =
        find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
                ptr_fun(isInteresting) ); 
                
    list<Widget*>::iterator iter =
        find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
                mem_fun(&Widget::isInteresting) ); 

item42: 通常, 要确保 模板 less<T> 与 operator< 语义相同

原因: less<T> 实现默认调用 T 的 operator<

但是, 若想让 less<Widget> 调用 Widget 的另一成员函数, 要对 less<T> 用 Widget 特化

例: 确保 智能指针的排序行为与它们的内置指针的排序行为相同

    namespace std
    {
        template<typename T>
        struct less<std::shared_prt<T> >
            : public binary_function<std::shared_prt<T>, std::shared_prt<T>, bool>
        {
            bool 
            operator()(const std::shared_prt<T>& lhs, 
                       const std::shared_prt<T>& lhs) const 
            {
                return less<T*>() ( lhs.get(), rhs.get() );
            }
        };
    }
    item46本在 part7

item46 STL算法参数考虑 函数对象 而不是函数(指针)

两大原因

1 STL算法操作参数若用函数对象, 且其operator()能inline成功, 则相对于用函数(指针)会带来 抽象性利益(abstraction bonus)

原因: STL(模板)算法(如 sort )实例化过程(编译阶段)中可以直接将函数对象的operator()的函数体inline进去; 而函数指针参数抑制了内联机制

算法内部通过指针发出的调用间接的函数调用, 大多数编译器不会试图对通过函数指针执行函数调用进行内联优化

(1) 内联: 类内实现=>隐式inline / 类外实现并用inline声明 => 显式inlime + 函数体短小 -> 才可能被编译器真正内联

(2) 函数参数会被编译器转化为函数指针参数

(3) 抽象性代价

C++ 相对于 C通常带来抽象性代价

    C++ 操作含内置类型(如 double)成员数据的对象

    比

    C直接操作内置类型(如 dounle)数据

    低效

(4) 抽象性利益的实例

[1] C++ STL算法用 函数对象参数 vs. 函数指针参数

inline 
bool myGreater(double d1, double d2)
{
    return d1 > d2;
}

struct MyGreater // 此例先不考虑可配接
{ 
    bool myGreater(double d1, double d2) // 类内实现 => 隐含 inline, 且应该能 inline 成功
    {
        return d1 > d2;
    }
}
sort(v.beign(), v.end(), MyGreater() );

<=> 用
sort(v.beign(), v.end(), std::greater<double>() );

比

sort(v.beign(), v.end(), myGreater);

快 160%(百万个double下)

##Note:

sort(v.beign(), v.end(), myGreater);
的 sort模板实例化时, 编译器生成的函数声明为 
void sort(...,
          bool (*comp)(double, double) );     

[2] C++的sort) 比 C的 qsort 高效

第3参数 可以是函数对象/只能是函数指针

2 编译器实现 + STL实现 可能不完全符合C++标准, 可能拒绝本合理的代码

例: 特定STL平台 处理const成员函数(如string::size)时, 用 mem_fun_ref 会报错

解决: 用函数对象 
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 空间配置器 分为第一级空间配置器,和第二级空间配置器 配合使用 第一级空间配置器分配大内存大于128bytes...
    陈星空阅读 1,270评论 0 1
  • 函数对象的概念 重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函...
    青春猪头少年_阅读 110评论 0 0
  • item1 慎重选择容器类型 1 STL容器分类 另一种分类方法: 连续内存/基于节点 的容器 (1) 连续内存容...
    my_passion阅读 254评论 0 1
  • 揭开使用STL时各 陷阱的 来源 / 解决方案的优劣 接口与实现分离: 对于STL, 不能简单地使用这条规则原因:...
    my_passion阅读 252评论 0 1
  • 1.C和C++的区别?C++的特性?面向对象编程的好处? 答:c++在c的基础上增添类,C是一个结构化语言,它的重...
    杰伦哎呦哎呦阅读 9,496评论 0 45