C++ 新特性学习(四) — Bind和Function

绑定函数是我认为C++新标准里第二有用的库了 绑定库的使用环境是:

  • GCC-C++ 4.3 及以上

  • Visual Studio 2008 SP1 及以上

  • BOOST 1.25及以上(其中function是1.16及以上)

先来看一段代码

#include <iostream>
#include <cstdio>
#include <memory>
#include <functional>

class button
{
public:

    std::function<void(int)> onClick;
};

class player
{
public:

    void play(void* sender, int param) {
        printf("Play: %d => %d\n", (int)sender, param);
    };
    void stop(void* sender, int param) {
        printf("Play: %d => %d\n", (int)sender, param);
    };
};

button playButton, stopButton;
player thePlayer;

void connect()
{
    playButton.onClick = std::bind(&player::play, &thePlayer, &playButton, std::placeholders::_1);
    stopButton.onClick = std::bind(&player::stop, &thePlayer, &stopButton, std::placeholders::_1);
}

int main () {
    connect();
    playButton.onClick(0);
    return 0;
}
// 以上代码参考boost中bind库示例代码,在G++ 4.6.1中测试通过

木有错,这是C++,并且很方便地实现了委托 这就是传说中的绑定库和增强型的函数对象 接下来一个一个来

Bind

可用于绑定函数、成员函数、函数对象、成员变量 这是老标准中std::bind1st和std::bind2nd的增强版,这两个函数只能且必须绑定一个带有两个参数的,并且只能且必须传入一个自定义参数。但是bind函数支持最多10个自定义参数。

int f(int a, int b)
{
    return a + b;
}

// 以下代码等价
std::bind1st(std::ptr_fun(f), 5)(x);   // 等于执行了 f(5, x)
std::bind(f, 5, std::placeholders::_1)(x);                     // 等于执行了 f(5, x)

在这里,std::bind函数返回绑定对象,后面跟的(x)表示传参x并执行。 std::placeholders::_1是占位符,表示调用时的第一个参数,这段代码里表示x 如果代码是std::bind(f, std::placeholders::_2, std::placeholders::_1)(x, y) 则表示执行 f(y, x) 另外,在调用前传入的函数参数会被复制并保存在std::bind返回的对象里,比如之前的std::bind(f, 5, std::placeholders::_1)中的5就被存储在了返回的对象里。所以为了性能上考虑,建议传入的类型为引用或指针,避免结构复制

另外,除了普通函数外,std::bind也支持成员函数,但是和普通函数不同,成员函数绑定的第二个参数必须是函数实例。如

struct X
{
    bool f(int a);
};

X x;
std::shared_ptr<X> p(new X);
int i = 5;

std::bind(&X::f, ref(x), std::placeholders::_1)(i);        // 相当于执行了 x.f(i)
std::bind(&X::f, &x, std::placeholders::_1)(i);            // 相当于执行了(&x)->f(i)
std::bind(&X::f, x, std::placeholders::_1)(i);            // 复制x,并执行(复制的x).f(i)
std::bind(&X::f, p, std::placeholders::_1)(i);            // 复制智能指针p,并执行(复制的p)->f(i)

由于第二个参数的函数实例的保存方式与参数一致,所以也建议传入类引用或类指针,或者智能指针。 另外,std::bind还可以用于绑定成员变量,和函数结构,绑定函数变量的方法类似成员函数,绑定函数结构的方法类似普通函数 再来一个std::bind稍微复杂一点的应用的例子,和算法库配合使用

void foo(int& i){
    -- i;
}

std::vector<int> vec;

// Blablabla..............

std::for_each(vec.begin(), vec.end(), std::bind(std::ref(foo)));

最后是bind函数的总结

函数使用形式:

  • bind(f [,t][,…]) // 自动推断

  • bind<返回值类型>(f [,t][,…]) // 非自动推断

  • bind<返回值类型, 函数类型, 绑定器传入参数类型>(f ,[…]) // 非自动推断

  • bind<返回值类型, 函数参数类型, 绑定器传入参数类型>(f [,…]) // 非自动推断

  • bind<返回值类型, 类, 绑定器传入参数类型(即对应的类实例)>(T::*f ,t [,…]) // 非自动推断

  • bind<返回值类型, 类, 函数参数类型, 绑定器传入参数类型(即对应的类实例+函数传入的参数), >(T::*f ,t [,…]) // 非自动推断

绑定组合

需要注意的问题:

  1. 绑定参数数量不匹配将会在绑定时编译错误(特别注意的是绑定类成员时遗漏类实例)

  2. 绑定参数类型不匹配将会在调用时编译错误

  3. 占位符不匹配将会在调用时编译错误

  4. 绑定对象必须是函数或成员函数指针

  5. 绑定对象默认为c++函数且不支持变长参数函数,如printf,某些编译器上extern “C”的函数(如: std::strcmp)也不支持(经过检测,G++和VC++都没问题)

  6. 支持”stdcall”, “cdecl”, “__fastcall” 和 “pascal” 前缀,但是绑定这些函数时要注意加一些定义(boost库是这样,tr1不知道)

  7. 对于函数重载的绑定,由于绑定时不能自动确定是哪一个函数,所以会绑定失败,可以使用类型转换或使用局部变量指定这些函数(VC++支持对重载函数的函数类型推断)

  8. 由于std::bind的函数参数类型推断和传入参数类型推断是分开的,所以如果函数的参数是引用类型,绑定参数的时候一定要用std::ref(详见 https://www.owent.net/2012/558.html ),否则会复制临时对象传入的,而不是传入引用类型。如:

    struct X
    {
    int& get();
    int const& get() const;
    };
    int main()
    {
    std::bind( static_cast< int const& (X::*) () const >( &X::get ), _1 );
    return 0;
    }
    // 或
    int main()
    {
    int const& (X::*get) () const = &X::get;
    std::bind( get, _1 );
    return 0;
    }
  9. bind函数的返回结果不包含STL中一元或二元函数的概念,因为其缺少result_type 和 argument_type 或 first_argument_type 和 second_argument_type的定义(经检测VC++和G++的bind返回结果包含result_type定义)(转换成std::function后如果是一元或二元函数则支持以上定义)

  10. 标准要求至少有10个占位符,而G++支持30个占位符

Function

这东西是针对函数对象的多态包装器(又称多态函数对象包装器),函数对象又称仿函数。 std::function的作用就在于把函数或函数对象转换成function对象,并用于保存和后期调用。 其中和std::bind的配合使用的例子上面已经有了,就不重复。 std::function同样支持函数、成员函数、函数变量和函数结构。 std::function和std::bind配合使用时是把std::bind返回的结果作为函数对象使用的。

函数对象的例程:

// 函数结构
struct int_div {
  float operator()(int x, int y) const { return x / y; };
};

std::function<float (int x, int y)> f = int_div();

但是成员变量和成员函数稍有不同,在申明时函数第一个类型必须是类的类型(或指针),传入参数是也同样。如:

// 类成员变量
struct X {
  int m;
};

X x;
x.m = 10;

std::function<int (X*) > f = &X::m;
printf("Function: class member %d\n", f(&x));

继续总结吧:

使用形式

std::function<函数申明>,如:std::function fn;

std::function和函数指针的优劣

  1. std::function 允许任意可转换的函数

  2. std::function 可以和其他参数或函数绑定对象库配合使用

  3. 当空函数结构调用的时候 std::function 的行为可以预见, 类型安全

  4. 函数指针更小

  5. 函数指针更快(std::function 在析构时可能会释放函数对象)

  6. 函数指针对C语言库的向后兼容性更好

  7. 函数指针的错误信息更容易理解

性能

  1. 对象大小: 包含两个函数指针的大小

  2. 复制性能: 取决于所关联的函数或函数对象,建议采用函数或函数对象的引用传给std::function来提高复制性能

  3. 执行性能: 对一个正常的内联编译器而言,将会通过函数指针执行函数调用。如果关联到函数对象,析构时会进释放该对象

Last updated