Boost库中的函数对象

《C++11/14高级编程:Boost程序库探秘》笔记

函数对象是一个定义了operator()的类,可以像普通函数一样被调用,又具有类的功能。

hash

散列函数,主要被用于实现各种无序散列容器。位于名字空间boost,头文件<boost/functional/hash.cpp>,它是一个非常简单的函数对象,类摘要如下:

template<class T>
struct hash: std::unary_function<T,std::size_t> //标准单参函数对象
{
    std::size_t operator()(T const& val) const;
};

hash对C++数据类型支持非常全面,包括以下几种类型:

  • char/wchar_t(但不支持C++11的char16_t/char32_t)
  • short/int/long等各种整数类型
  • bool类型
  • float/double等浮点数类型
  • long long和long double
  • 指针和数组
  • std::string/std::wstring
  • 扩展支持pair、complex结构和array、vector、list等标准容器

除以上外,hash库不支持其他类型,包括标准容器适配器(stack、queue、priority_queue)和所有boost容器。
用法很简单,只需要创建一个实例,然后像使用函数一样调用它的operator()就行:

cout << hash<int>()(0x2000) << endl;    //计算整数的散列值
cout << hash<double>()(1.732) << endl;  //计算浮点数的散列值
cout << hash<const char*>()("string") << endl;  //计算字符数组的散列值
cout << hash<string>()("string") << endl;   //计算标准字符串的散列值

complex<double> c(1.0,2.0);
cout << hash<decltype(c)>()(c) << endl; //计算复数的散列值

map<int,string> m;
cout << hash<decltype(m)>()(m) << endl; //计算标准映射容器的散列值

array<int,5> ar;
cout << hash<decltype(ar)>()(ar) << endl;

//用于无序容器的模板参数
boost::unordered_set<int,boost::hash<int> >         us;
boost::unordered_map<int,string,boost::hash<int> >  um;

hash函数对象实际上只是一个简单的包装类,真正的散列值计算是在boost名字空间里的hash_value()函数实现,而hash_value()则针对各种数据类型定义了不同的重载,最后hash再对不同的类型使用模板特化来调用不同重载形式的hash_value()。
所以,如果需要实现对自定义类型计算散列值,那么只要在类中定义一个成员函数hash_value()并计算散列值,然后再在外部实现重载的自由函数hash_value(),比如:

class person final
{
private:
    int             id;
    string          name;
    unsigned int    age;
public:
    person(int a,const char* b,unsigned int c):
    id(a),name(b),age(c){}

    size_t hash_value() const   //自定义的散列计算函数
    {
        return hash<int>()(id);
    }
};
size_t hash_value(person const & p)
{
    return p.hash_value();
}

//使用
person p(1,"adam",20);
cout << hash<person>()(p) << endl;

如果是要对多个目标计算散列值,那么可以使用hash库提供的辅助函数hash_combine()和hash_range()。

template<typename T>
void hash_combine(size_t & seed,T const & v);

template<typename It>
std::size_t hash_range(It first,It last);

template<typename It>
void hash_range(std::size_t& seed,It first,It last);

hash_combine()使用一个变量seed作为初始输入参数,可以对多个变量连续调用,最终得到的散列值再从seed输出。散列值的计算与hash_combine的运算顺序有关,不同计算顺序得到散列值不同。

size_t hash_value() const
{
    size_t seed = 1984;  //可以是任意整数
    hash_combine(seed,id);
    hash_combine(seed,name);
    hash_combine(seed,age);
    return seed;
}

hash_range()是另外一种组合散列值的方式,它对迭代器区间内的所有元素调用hash_combine()计算,如果不给初始seed赋值,seed默认为0。


mem_fn

同bind类似,可以调用对象或对象指针的任意成员函数或成员变量,支持多达8个参数,已被收入C++11标准。
同bind一样,mem_fn不是一个单一的模板函数,而是有很多重载以支持各种情况。它比较像一个简化版的bind,只能绑定类的成员函数或成员变量。

template<class R,class T> mf<R,T> mem_fn(R T::*f);

template<class R,class T> class mf
{
public:
    R & operator()(T * p,...) const;  //调用对象的指针
    R & operator()(T & t,...) const;  //调用对象的引用
}

mem_fn不仅支持普通对象,也支持对象指针和只能指针,对于智能指针,mem_fn会使用boost::get_pointer()获取真正的指针,不会导致所有权转移或者引用计数增加,非常安全。

class demo_class
{
public:
    int x;
    demo_class(int a = 0):x(a){}

    void print()
    {
        cout << x << endl;
    }

    void hello(const char* str)
    {
        cout << str << endl;
    }
}

//使用mem_fn
demo_class d(1);
mem_fn(&demo_class::print)(d);          //绑定普通对象,调用无参成员函数

demo_class *p = &d;
mem_fn(&demo_class::hello)(p,"hello");  //绑定对象指针,调用单参成员函数

unique_ptr<demo_class> up(new demo_class(100));
mem_fn(&demo_class::print)(up);         //绑定unique_ptr

assert(up.get() != 0);                  //指针的所有权没有转移

shared_ptr<demo_class> sp(new demo_class);
mem_fn(&demo_class::hello)(sp,"world");

std::vector<demo_class> v(10);
std::for_each(v.begin(),v.end(),mem_fn(&demo_class::print));

cout << mem_fn(&demo_class::x)(d) << endl;

跟bind的对比,很多情况下,bind都可以代替mem_fn,需要增加一个”_1”占位符用于传递对象。

demo_class d;
bind(&demo_class::print,_1)(d);
bind(&demo_class::hello,_1,"hello")(&d);

std::vector<demo_class> v(10);
std::for_each(v.begin(),v.end(),bind(&demo_class::hello,_1,"world"));

factory

factory用函数对象封装了对象的创建过程,消除关键字new的随意使用,基本相当于new T(),可以直接创建T*指针,如果说checked_delete是智能delete,那么factory就是智能new。

template<typename Pointer,
        class Allocator,factory_alloc_propagation>
class factory
{
public:
    typedef typename boost::remove_cv<Pointer>::type        result_type;  //函数对象调用后返回的类型,即创建的指针类型
    typedef typename boost::pointee<result_type>::type      value_type;  //指针指向的值类型
    inline result_type operator()()const;  //最多支持10个参数
    ...
}

factory有三个模板参数,一般使用第一个模板参数Pointer,其他两个可以用缺省值。Pointer是被创建的“指针”类型,它不仅支持创建原始指针,也能够创建智能指针unique_ptr和shared_ptr。

使用的时候,factory要求被创建的类型T至少要有一个public的构造函数,否则无法访问不能完成创建工作。

//无参创建指针
auto pi = factory<int*>()();        //两对括号
auto ps = factory<string*>()();
auto pp = factory<pair<int,double>*>()();

采用无参创建时factory将调用value_type的缺省构造函数完成对象初始化,由于factory不是函数而是函数对象,因此必须先用一对括号调用它的构造函数,创建出factory的一个临时对象,然后再用第二对括号调用它的operator()来创建所需的对象。
factory创建出的指针可以用delete操作符删除,当然最好用对应的checked_delete。

//创建智能指针
auto up = factory<unique_ptr<int> >()();
auto sp = factory<shared_ptr<string> >()();

创建智能指针要完全写出智能指针的类型,但注意不能创建boost:scoped_ptr,因为scoped_ptr不支持拷贝转移语义。

//带参数创建指针
int a = 10,b = 20;
auto pi = factory<int*>()(a);
auto ps = factory<string*>()("char* lvalue");   //字符串是左值
auto pp = factory<pair<int,int>*>()(a,b);

auto pi2 = factory<int*>()(10);             //使用右值,error
auto pp2 = factory<pair<int,int>*>()(1,2);  //使用右值,error

带参数创建支持使用最多10个参数,参数将传递给类的构造函数完成初始化,但是缺陷是参数必须要是左值类型,传递右值无法编译,这将导致需要声明若干临时变量,麻烦。弥补的方法是使用bind包装factory函数对象,因为bind对参数类型没有限制,它内部持有的参数拷贝,可被用作左值,就是语法复杂一点
auto p = bind(factory<int*>(),10)();

value_factory
value_factory是factory库提供的另一个函数对象,同样可以创建对象,用法也和factory相似,只是它创建的是真正的对象实例,而不是指针。

template<typename T>
class value_factory
{
public:
    typedef T           result_type;
    inline result_type  operator()() const;
}

它的类声明和实现都比factory简单,仅有一个typedef,同样最多支持传入10个参数,也同样要求左值,但同样可以用bind解决右值问题,operator()将返回创建对象的拷贝,因此类型T必须支持拷贝构造。

auto i = value_factory<int>()();
auto str = value_factory<string>()("hello");
auto p = value_factory<pair<int,string>>()(i,str);
auto i2 = bind(value_factory<int>(),10)();

猜你喜欢

转载自blog.csdn.net/zuolj/article/details/78689923