The C++ standard library(侯捷/孟岩 译) 08--functors

8 functors/functor objects

8.1 conception of functor

functor,是定义了operator()的对象。

eg:
FunctionObjectType fo;
// ...
fo(...)

其中fo()调用仿函数fo的operator()而非函数fo()。

可将functor视为一般函数,但更复杂,eg:
class FunctonObjectType
{
    public:
        void operator() (){  // statements }
};

functor定义形式复杂,但优点:

1. functor较一般函数更灵巧。因为functor可有两个状态不同的实体。
2.functor有其类型,故可将其类型作为template参数。
3. functor执行速度更快。
8.1.1 functor作sort criteria
/* 需将某些class objects以已序形式置于容器中。
 * 但或不想或不能,总之无法使用一般的operator<来排序。
 * 要以某种特定规则(常基于某些成员函数)来排序,此时可用functor。
 */
// fo/sort1.cpp

#include <iostream>
#include <string>
#include <set>
#include <algorithm>
using namespace std;

class Person
{
    public:
        string firstname() const;
        string lastname() const;
        // ...
};

/* class fot function predicate
 * -operator() returns whether a person is less than another person
 */
class PersonSortCriterion
{
    public:
        bool operator() (const Person& p1, const Person& p2) const
        {
            /* a person is less than another person
             * - if the last name is less
             * - if the last name is equal and the first name is less
             */
            return p1.lastname() < p2.lastname() ||
                (!(p2.lastname() < p1.lastname()) &&
                 p1.firstname() < p2.firstname() );
        }
};

int main()
{
    // declare set type with special sorting criterion
    typedef set<Person, PersonSortCriterion> PersonSet;

    // create such a collection
    PersonSet col1;
    //...
    
    // do something with the elements
    PersonSet::iterator pos;
    for(pos = col1.begin(); pos != col1.end(); ++pos)
    {
        //...
    }
    // ...
}

前面有相关代码,可参考p199。

8.1.2 拥有internal state的functor
// 展示functor 模拟函数在同一时刻下拥有多个状态
// fo/genera1.cpp

#include <iostream>
#include <list>
#include <algorithm>
#include "../stl/print.hpp"
using namespace std;

class IntSequence
{
    private:
        int value;
    public:
        // constructor
        IntSequence (int initialValue) : value(initialValue){}

        // "function call"
        int operator() ()
        {
            return value++;
        }
};

int main()
{
    list<int> col1;

    // insert value from 1 to 9
    generate_n (back_inserter(col1),    // start
            9,  // number of elements
            IntSequence(1) );   // generate values

    PRINT_ELEMENTS(col1);

    // replace second to last element but one with values
    // starting at 42
    generate (++col1.begin(),   // start
            --col1.end(),   // end
            IntSequence(42) );  // generate values
    PRINT_ELEMENTS(col1);
}

output:
general.png

functor是passed by value,而不是passed by reference: passed by value优点是可传递常量或临时表达式,缺点不能改变随参数而来最终的functor state。
有两种办法从“运行了functor”的算法中获得结果:

    1. by reference方式传递functor。
    1. 使用for_each()。(见下一节)
// fo/genera2.cpp

#include <iostream>
#include <list>
#include <algorithm>
#include "../stl/print.hpp"
using namespace std;

class IntSequence
{
    private:
        int value;
    public:
        // constructor
        IntSequence(int initialValue) : value(initialValue){}

        // "function call"
        int operator() ()
        {
            return value++;
        }
};

int main()
{
    list<int> col1;
    IntSequence seq(1); // integral sequence starting with 1

    // insert value from 1 to 4
    // - pass function object by reference
    // so that it will continue with 5
    generate_n<back_insert_iterator<list<int> >, 
        int, IntSequence& >(back_inserter(col1) ,   // start
                4,  // number of elements
                seq);   // generate values
    PRINT_ELEMENTS(col1);

    // insert values from 42 to 45
    generate_n(back_inserter(col1), // start
            4,  // number of elements
            IntSequence(42));   // generates values
    PRINT_ELEMENTS(col1);

    // continue with first sequence
    // - pass function object by value
    // so that it will continue with 5 again
    generate_n(back_inserter(col1), 4,seq);
    PRINT_ELEMENTS(col1);

    // continue with first sequence again
    generate_n(back_inserter(col1), 4, seq);
    PRINT_ELEMENTS(col1);
}

output:
genera2.png
8.1.3 for_each()返回值

for_each()可以返回其functor,其它算法都无该特性。

// 使用for_each()返回值,处理一个序列的平均值

// fo/foreach3.cpp

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// function object to process the mean value
class MeanValue
{
    private:
        long num;   // num of elements
        long sum;   // sum of all element values
    public:
        // constructor
        MeanValue() : num(0), sum(0){}

        // "function call"
        // - process one more elements of the sequence
        void operator() (int elem)
        {
            num++;  // increment
            sum += elem;    // add value
        }

        // return mean value
        double value() 
        {
            return static_cast<double>(sum)/static_cast<double>(num);
        }
};

int main()
{
    vector<int> col1;

    // insert elements from 1 to 8
    for(int i = 1; i <= 8; ++i)
    {
        col1.push_back(i);
    }

    // process and print mean value
    MeanValue mv = for_each(col1.begin(), col1.end(), MeanValue());
    cout << "mean value: " << mv.value() << endl;
}

程序说明:返回的functor被赋值给mv,故可调用mv.value()查询其状态。

output:
foreach3.png
8.1.4 predicates && functors

predicate,即返回bool值的一个函数或functor,但对STL而言并非所有返回bool的函数都是合法的predicate

// fo/removeif.cpp

#include <iostream>
#include <list>
#include <algorithm>
#include "../stl/print.hpp"
using namespace std;

class Nth
{   // function object that returns true for the nth call
    private:
        int nth;    // call for which to return true
        int count;
    public:
        Nth(int n) : nth(n), count(0){}
        bool operator() (int)
        {
            return ++count == nth;
        }
};

int main()
{
    list<int> col1;

    // insert elements from 1 to 9
    for (int i = 1; i <= 9; ++i)
    {
        col1.push_back(i);
    }
    PRINT_ELEMENTS(col1, "col1: ");

    // remove third element
    list<int>::iterator pos;
    pos = remove_if(col1.begin(), col1.end(), Nth(3));
    col1.erase(pos, col1.end());
    PRINT_ELEMENTS(col1, "nth removed: ");
}

output:
removeif.png

结果及说明:

结果删除了第3和第6个元素,
  原因在于remove_if()标准实现内部保留predicate的一份副本:
template <class ForwIter, class Predicate>
ForwIter std::remove_if(ForwIter beg, ForwIter end, Predicate op)
{
    beg = find_if(beg, end, op);
    if (beg == end)
    {
        return beg;
    }
    else
    {
        ForwIter next = beg;
        return remove_copy_if(++next, end, beg, op);
    }
}

remove_if()使用find_if()来查找应被移除的第一个元素,
  然后使用predicate op的副本处里剩余的元素,
    故原始状态的Nth再次被调用,即会移除剩余元素的第三个元素。

由上,predicate不应该因为被调用而改变自身状态
当然,避免上述问题可改变下remove_if()的标准实现:

beg = find_if(beg, end, op); 语句替换为
while(beg != end && !op(*beg)){  ++beg;}

8.2 预定义的functor

使用这些functors,须#include <functional>


t8-1.png

bind1st(op, para)指将para绑定到op的第一个参数,bind2nd(op,para)指将para绑定到op的第二个参数。(可见p311的fopow1.cpp)

8.2.1 function adapter(函数配接器)

function adapter指能 将functor和另一个functor(或某个值或一般函数)结合起来的functor。function adapter也声明于<functional>。

t8-2.png

functional compositon(功能复合、函数复合):把多个functor结合起来形成强大的表达式。

8.2.2 对成员函数设计的函数配接器

C++标准库提供了一些额外的函数配接器,通过它们,可对群集内每个元素调用其成员函数。


t8-3.png

eg:

// 利用mem_fun_ref,对vector内每个对象调用其成员函数
// fo/memfun1a.cpp

class Person
{
    private:
        std::string name;
    public:
        // ...
        void print() const
        {
            std::cout << name << std::endl;
        }
        void printWithPrefix (std::string prefix) const
        {
            std::cout << prefix << name << std::endl;
        }
};

void foo(const std::vector<Person>& col1)
{
    using std::for_each;
    using std::bind2nd;
    using std::mem_fun_ref;

    // call member function print() for each element
    for_each(col1.begin(), col1.end(),
            mem_fun_ref(&Person::print));

    // call member function printWithPrefix() for each element
    // - "person: " is passed as an argument to the member function
    for_each(col1.begin(), col1.end(),
            bind2nd(mem_fun_ref(&Person::printWithPrefix), "person: ") );
}

不能直接把一个成员函数传给算法,for_each()会针对第三参数传来的指针调用operator() 而非调用该指针所指的函数(可见p126的for_each()实现)。用配接器会将operator()调用操作做适当转换。

eg:
for_each(col1.begin, col1.end(), &Person::print);  
// error:can't call operator() for a member function potiner
8.2.3 对一般函数设计的函数配接器
t8-4.png
bool check(int elem);
pos = find_if(col1.begin(), col1.end(), 
              not1(ptr_fun(check) ) );  // 查找第一个令检验失败的元素

pos = find_if (col1.begin(), col1.end(),   
               bind2nd(ptr_fun(strcmp), "") ) ;
// 采用strcmp()将每个元素与空的C-string比较。
8.2.4 user-defined functor使用function adapter

希望user-defined functor能和function adapter搭配使用,须满足一些条件:提供类型成员反映其参数和返回值类型
C++标准库提供了一些结构:

template <class Arg, class Result>
struct unary_function
{
    typedef Arg argument_type;
    typedef Result result_type;
};

template <class Arg1, class Arg2, class Result>
struct binary_function
{
    typedef Arg1 first_argument_type;
    typedef Arg2 second_argument_type;
    typedef Result result_type;
};

如此只要继承上述某种形式,即可满足adaptable条件。
eg:

// fo/fopow.hpp

#include <funcitonal>
#include <cmath>

template <class T1, class T2>
struct fopow : public std::binary_function<T1, T2, T1>
{
    T1 operator() (T1 base, T2 exp) const
    {
        return std::pow(base, exp);
    }
};


// fo/fopow1.cpp

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
using namespace std;

#include "fopow.hpp"

int main()
{
    vector<int> col1;

    for (int i = 1; i <= 9; ++i)
    {
        col1.push_back(i);
    }

    // print 3 raised to the power of all elements
    transform(col1.begin(), col1.end(),
            ostream_iterator<int>(cout, " "),
            bind1st(fopow<float,int>(), 3));
    cout << endl;

    // print all elements raised to the power of 3
    transform(col1.begin(), col1.end(),
            ostream_iterator<int>(cout, " "),
            bind2nd(fopow<float,int>(), 3));
    cout << endl;
}

output:
fopow1.png

8.3 组合型functor

一般而言,所有函数行为都可由functor组合实现。但C++标准库未提供足够的配接器来实现。
理论上,下面的compose adapter(组合型配接器)很有用。

f( g(elem) )
一元判别式g()结果作为一元判别式f()参数。

f( g(elem1, elem2) )
二元判别式g()结果作为一元判别式f()参数。

f( g(elem1), h(elem2) )
一元判别式g()和h()的结果作为二元判别式f()的参数。

但上述compose adapter未被纳入标准。书中作者自定义名称:

t8-5.png

8.3.1 unary compose function object adapter
  • 以compose_f_gx进行nested(嵌套)计算
    compose_f_gx在SGI STL实现版本称为compose1,compose_f_gx的可实现如下:
// fo/compose11.hpp

#include <functional>

/* class for the compose_f_gx adapter */
template <class OP1, class OP2>
class compose_f_gx_t :
    public std::unary_function<typename OP2::argument_type, typename OP1::result_type>
{
    private:
        OP1 op1;    // process: op1(op2(X))
        OP2 op2;
    public:
        // constructor
        compose_f_gx_t(const OP1& o1, const OP2& o2) : op1(o1), op2(o2){}

        // function call
        typename OP1::result_type
            operator() (const typename OP2::argument_type& x) const
            {
                return op1(op2(X));
            }

};
/* conveniences functions for the compose_f_gx adapter */
template <class OP1, class OP2>
    inline compose_f_gx_t<OP1, OP2>
    compose_f_gx (const OP1& o1, const OP2& o2)
{
    return compose_f_gx_t<OP1, OP2>(o1, o2);
}
// 对一个序列的每个元素先 加10 再乘5
// fo/compose1.cpp

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <iterator>
#include "../stl/print.hpp"
#include "compose11.hpp"
using namespace std;

int main()
{
    vector<int> col1;

    // insert elements from 1 to 9
    for (int i = 1; i <= 9; ++i)
    {
        col1.push_back(i);
    }
    PRINT_ELEMENTS(col1);

    // for each element add 10 and multiply by 5
    transform (col1.begin(), col1.end(),
            ostream_iterator<int>(cout, " "),
            compose_f_gx(bind2nd(multiplies<int>(), 5),
                        bind2nd(plus<int>(), 10) ) );
    cout << endl;
}

output:
compose1.png
  • 以compose_f_gx_hx组合两个准则
    compose_f_gx_hx在SGI STL实现版本称为compose2,compose_f_gx_hx可实现如下:
// fo/compose21.hpp

#include <functional>

/* class for the compose_f_gx_hx adapter */

template <class OP1, class OP2, class OP3>
class compose_f_gx_hx_t :
    public std::unary_function<typename OP2::argument_type, typename OP1::result_type>
{
    private:
        OP1 op1;    // process: op1(op2(x), op3(x))
        OP2 op2;
        OP3 op3;
    public:
        // constructor
        compose_f_gx_gx_t (const OP1& o1, const OP2& o2, const OP3& o3) :
            op1(o1), op2(o2), op3(o3){}

        // function call
        typename OP1::result_type
            operator() (const typename OP2::argument& x) const
            {
                return op1(op2(x), op3(x));
            }
};

/* convenience functions for the compose_f_gx_hx adapter */
template <class OP1, class OP2, class OP3>
inline compose_f_gx_hx_t<OP1, OP2, OP3>
compose_f_gx_hx (const OP1& o1, const OP2& o2, const OP3& o3)
{
    return compose_f_gx_hx_t<OP1, OP2, OP3>(o1, o2, o3);
}
// 删除序列中大于4且小于7 的元素
// fo/compose2.cpp

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include "../stl/print.hpp"
#include "compose21.hpp"
using namespace std;

int main()
{
    vector<int> col1;

    // insert elements from 1 to 9
    for (int i = 1; i <= 9; ++i)
    {
        col1.push_back(i);
    }
    PRINT_ELEMENTS(col1);

    // remove all elements that are greater than four and less than seven
    // - retain new end
    vector<int>::iterator pos;
    pos = remove_if(col1.begin(), col1.end(),
            compose_f_gx_hx(logical_and<bool>(),
                            bind2nd(greater<int>(), 4),
                            bind2nd(less<int>(),7) ) );
    // remove "removed" elements in col1
    col1.erase(pos, col1.end());
    PRINT_ELEMENTS(col1);
}

output:
compose2.png
8.3.2 binary compose function object adapters

二元组合函数配接器,可将两个一元运算(分别接受不同参数)的结果加以处理,称之为compose_f_gx_hy,可能的实现如下:

// fo/compose22.hpp

#include <functional>

/* class for the compose_f_gx_hy adapter */
template <class OP1, class OP2, class OP3>
class compose_f_gx_hy_t :
    public std::binary_function<typename OP2::argument_type, 
                                typename OP3::argument_type,
                                typename OP1::result_type>
{
    private:
        OP1 op1;    // process: op1(op2(x), op3(y))
        OP2 op2;
        OP3 op3;
    public:
        // constructor
        compose_f_gx_hy_t(const OP1& o1, const OP2& o2, const OP3& o3) :
            op1(o1), op2(o2), op3(o3){}

        // function call
        typename OP1::result_type
            operator() (const typename OP2::argument_type& x,
                    const typename OP3::argument_type& y) const
            {
                return op1(op2(x), op3(y));
            }
};

/* convenience function for the compose_f_gx_hy adapter */
template <class OP1, class OP2, class OP3>
inline compose_f_gx_hy_t<OP1, OP2, OP3>
compose_f_gx_hy(const OP1& o1, const OP2& o2, const OP3& o3)
{
    return compose_f_gx_hy_t<OP1,OP2, OP3>(o1, o2, o3);
}
// fo/compose22.hpp

#include <functional>

/* class for the compose_f_gx_hy adapter */
template <class OP1, class OP2, class OP3>
class compose_f_gx_hy_t :
    public std::binary_function<typename OP2::argument_type, 
                                typename OP3::argument_type,
                                typename OP1::result_type>
{
    private:
        OP1 op1;    // process: op1(op2(x), op3(y))
        OP2 op2;
        OP3 op3;
    public:
        // constructor
        compose_f_gx_hy_t(const OP1& o1, const OP2& o2, const OP3& o3) :
            op1(o1), op2(o2), op3(o3){}

        // function call
        typename OP1::result_type
            operator() (const typename OP2::argument_type& x,
                    const typename OP3::argument_type& y) const
            {
                return op1(op2(x), op3(y));
            }
};

/* convenience function for the compose_f_gx_hy adapter */
template <class OP1, class OP2, class OP3>
inline compose_f_gx_hy_t<OP1, OP2, OP3>
compose_f_gx_hy(const OP1& o1, const OP2& o2, const OP3& o3)
{
    return compose_f_gx_hy_t<OP1,OP2, OP3>(o1, o2, o3);
}

output:
compose3.png

除p319,p499有“大小写无关”子串查找但未用compose_f_gx_hy。

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

推荐阅读更多精彩内容