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  
        unary_negate 
    not1 (const Predicate& pred) // 模板函数, 对可配接对象, 取否定后返回的仍是可配接对象
    {
        return unary_negate(pred);
    }

    template  
    class unary_negate
        : public unary_function 
    {
    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::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 
        pointer_to_unary_function 
    ptr_fun (Result (*pf)(Arg))
    {
        return pointer_to_unary_function(pf);
    }

    template 
    class pointer_to_unary_function 
        : public unary_function 
    {
    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
    {
        bool
        operator()(const T& lhs, const T& lhs) const;
    };
    
    struct TPtrTNameCompare
        : public std::binary_function
    {
        bool
        operator()(const T* lhs, const T* rhs) const;
    };

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

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

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

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

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

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

如前

[2] 用函数对象

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

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

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

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

#include 
#include 
#include 
#include 

using namespace std;

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

template
struct IsInteresting
    : public std::unary_function // 与 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 widgetPtrLst;
    widgetPtrLst.push_back(new Widget(10));
    widgetPtrLst.push_back(new Widget(3) );
    widgetPtrLst.push_back(new Widget(2) );

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

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

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

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

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

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

    set strSet; // item19

    typedef set 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
        mem_fun_t
    mem_fun(ReturnType (C::*pmf)() )
    { return mem_fun_t(pmf); }

    template 
    class mem_fun_t : public unary_function 
    {
        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
    /* see below */ 
    operator()(Args&&... args) /* cvref-qualifiers */
        noexcept(/* see below */);
    list::iterator iter =
        find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
                ptr_fun(isInteresting) ); 
                
    list::iterator iter =
        find_if(widgetPtrLst.begin(), widgetPtrLst.end(),
                mem_fun(&Widget::isInteresting) ); 

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

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

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

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

    namespace std
    {
        template
        struct less >
            : public binary_function, std::shared_prt, bool>
        {
            bool 
            operator()(const std::shared_prt& lhs, 
                       const std::shared_prt& lhs) const 
            {
                return less() ( 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() );

比

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 会报错

解决: 用函数对象 

你可能感兴趣的:(Effective STL-6 遍布STL的 functor)