1. 程式人生 > >C11簡潔之道:函數綁定

C11簡潔之道:函數綁定

分享 bool 執行 通過 return 類型 n-1 ++ oid

1、 可調用對象

  在C++中,有“可調用對象”這麽個概念,那麽什麽是調用對象呢?有哪些情況?我們來看看:

  • 函數指針;
  • 具有operator()成員函數的類對象(仿函數);
  • 可以被轉換為函數指針的類對象;
  • 類成員(函數)指針。

  我們來看代碼:

//函數指針

void func(void)
{
    //...
}

struct Foo
{
    void operator()(void)
    {
        //...
    }
};

struct Bar
{
    using fr_t = void(*)(void);
    static void func(void
) { //... } operator fr_t(void) { return func; } }; struct A { int mia; void mem_func(void) { //... } }; int main(void) { //函數指針 void(* func_ptr)(void) = &func; func_ptr(); //仿函數 Foo foo; foo(); //被轉為指針的類對象 Bar bar; bar();
//類成員函數指針 void (A::*mem_func_ptr)(void) = &A::mem_func; //類成員指針 int A::*mem_obj_ptr = &A::mia; A aa; (aa.*mem_func_ptr)(); aa.*mem_obj_ptr = 123; return 0; }

  上述的對象都是可調用對象,這些對象的類型統稱為“可調用類型”。這些可調用對象都具有統一的操作形式,除了類成員指針之外,都是通過括號的方式來進行調用,但是定義的方法比較多,在C++11中增加了std::function來進行函數對象的調用。

2、 std::function

  std::function是一個可調用對象的包裝器,他是一個類模板,可以容納除了類成員(函數)指針之外的所用可調用對象,通過指定他的模板參數,可以以統一的方式處理函數、函數對象、函數指針,並允許保存或者延遲執行。

  當我們給std::function填入合適的函數簽名(即一個函數類型,只需要包括返回值和參數列表)之後,它就變成了一個可以容納所有這一類調用方式的“函數包裝器”。

#include <iostream>
#include <functional>

void func(void)
{
    std::cout << __FUNCTION__ << std::endl;
}

class Foo
{
public:
    static int foo_func(int a)
    {
        std::cout << __FUNCTION__ << "(" << a << ")->: ";
        return a;
    }
};

class Bar
{
public:

    int operator()(int a)
    {
        std::cout << __FUNCTION__ << "(" << a << ")->: ";
        return a;
    }
};

int main(void)
{
    //綁定一個普通函數
    std::function<void(void)> fr1 = func;
    fr1();

    //綁定一個靜態成員函數
    std::function<int(int)> fr2 = Foo::foo_func;
    std::cout << fr2(111) << std::endl;

    //綁定一個仿函數
    Bar bar;
    fr2 = bar;
    std::cout << fr2(111) << std::endl;

    return 0;
}

  執行結果:

技術分享

  std::function還可以取代函數指針的作用,因為它可以保存函數延遲執行,所以也適合做回調函數。

#include <iostream>
#include <functional>

class A
{
    std::function<void()> callback;
public:
    A(const std::function<void()> &f) : callback(f){}

    void notify(void)
    {
        callback();
    }
};

class Foo
{
public:
    void operator()(void)
    {
        std::cout << __FUNCTION__ << std::endl;
    }
};
 
int main(void)
{
    Foo foo;
    A aa(foo);
    aa.notify();

    return 0;
}

  std::function還可以作為函數入參,比普通函數指針更加靈活和便利。

#include <iostream>
#include <functional>

void call_when_event(int x, const std::function<void(int)>& f)
{
    if(!(x & 1)) //x % 2 == 0
    {
        f(x);
    }
}

void output(int x)
{
    std::cout << x << "  ";
}

int main(void)
{
    for(int i = 0; i < 10; i++)
    {
        call_when_event(i, output);
    }

    std::cout << std::endl;

    return 0;
}

3、 std::bind綁定器

3.1 std::bind綁定器

  std::bind用來將可調用對象與起參數一起進行綁定,綁定的結果使用std::function進行保存,並在我們需要調用的時候調用。它主要有兩大作用:

  • 將可調用對象和參數綁定成為一個仿函數;
  • 將多元(參數個數為n,n-1)可調用對象轉換成一元或者(n-1)元可調用對象,即只綁定部分對象。

  我們來看實際使用:

#include <iostream>
#include <functional>

void call_when_event(int x, const std::function<void(int)>& f)
{
    if(!(x & 1)) //x % 2 == 0
    {
        f(x);
    }
}

void output(int x)
{
    std::cout << x << "  ";
}

void output2(int x)
{
    std::cout << x + 2 << "  ";
}

int main(void)
{
    {
        auto fr = std::bind(output, std::placeholders::_1);
        for(int i = 0; i < 10; i++)
        {
            call_when_event(i, fr);
        }

        std::cout << std::endl;
    }
    {
        auto fr = std::bind(output2, std::placeholders::_1);
        for(int i = 0; i < 10; i++)
        {
            call_when_event(i, fr);
        }

        std::cout << std::endl;
    }

    return 0;
}

  通過代碼我們可以知道std::bind在函數外部通過綁定不同的函數,控制執行結果。這裏我們還使用了std::placeholders占位符來綁定函數參數。

3.2 std::placeholders

  通過std::placeholders占位符綁定函數參數,使得std::bind的使用非常靈活。std::placeholders決定函數占用位置取用輸入參數的第幾個參數。

#include <iostream>
#include <functional>

void output(int x, int y)
{
    std::cout << x << "  " << y << std::endl;
}

int main(void)
{
    std::bind(output, 1, 2)();                                                 //輸出:1 2
    std::bind(output, std::placeholders::_1, 2)(1);                         //輸出:1 2
    std::bind(output, 2, std::placeholders::_1)(1);                            //輸出:2 1
    //std::bind(output, 2, std::placeholders::_2)(1);                         //error,沒有第二個參數
    std::bind(output, 2, std::placeholders::_2)(1,2);                         //輸出:2 2,第一個參數被拋棄
    std::bind(output, std::placeholders::_1, std::placeholders::_2)(1,2);     //輸出:1 2
    std::bind(output, std::placeholders::_2, std::placeholders::_1)(1,2);     //輸出:2 1       

    return 0;
}

3.3 std::bind+std::function

  我們先看一組例子:

#include <iostream>
#include <functional>

class A
{
public:
    int mi = 0;

    void output(int x, int y)
    {
        std::cout << x << "  " << y << std::endl;
    }
};

int main(void)
{
    A a;
    std::function<void(int, int)> fr = std::bind(&A::output, &a, std::placeholders::_1, std::placeholders::_2);
    fr(1, 2);   

    std::function<int &(void)> fr_i = std::bind(&A::mi, &a);
    fr_i() = 123;
    std::cout << a.mi << std::endl;

    return 0;
}

  fr的類型是std::function<void(int, int)>,我們通過std::bind將A的成員函數output的指針和a綁定,並轉換為一個仿函數存儲在fr中。

  通過std::bind將A的成員mi的指針和a綁定,返回的結果放入類型為std::function<int &(void)>的fr_i中,可以在需要的時候修改這個成員的值。

3.4 改善標準函數

  假如我們有一個這樣的需求,對某個集合裏面的元素進行統計,假設元素類型為int,那麽我們需要對類型做比較,必須有一個閥值,即大於或者小於這個數。這裏我們可以通過標準庫的函數來實現。

#include <iostream>
#include <functional>

int main()
{
    std::vector<int> coll;
    for (int i = 1; i <= 10; ++i)
    {
        coll.push_back(i);
    }

    // 查找元素值大於10的元素的個數
    // 也就是使得10 < elem成立的元素個數
    int res = count_if(coll.begin(), coll.end(), std::bind1st(less<int>(), 10));
    cout << res << endl;

    // 查找元素值小於10的元素的個數
    // 也就是使得elem < 10成立的元素個數
    res = count_if(coll.begin(), coll.end(), std::bind2nd(less<int>(), 10));
    cout << res << endl;

    bool b = less<int>(10, 20); // 返回true

    return 0;
}

  本質上是對一個二元函數less<int>的調用,但是要分別調用bind1st,bind2nd,用起來比較繁雜,現在我們有bind,可以用統一的方式去實現。並不用關心是bind1st還是bind2nd,用bind即可。

#include <iostream>
#include <functional>

int main()
{
    using std::placeholders::_1;
    std::vector<int> coll;

    //查找元素值大於10的元素個數
    int count = std::count_if(coll.begin(), coll.end(), std::bind(less<int>(), 10, _1));

    //查找元素值小於10的元素個數
    count = std::count_if(coll.begin(), coll.end(), std::bind(less<int>(), _1, 10));

    return 0;
}

3.5 組合使用

  bind可以綁定多個函數,假設我們要對某個集合在大於5小於10的元素個數進行統計,我們該怎麽封裝呢?

  首先封裝一個判斷是否大於5的函數,使其輸入只有一個參數,直接和5比較,大於5返回true。

std::bind(std::greater<int>(), std::placeholders::_1, 5);

  同樣,我們需要封裝一個判斷是否小於10的函數,使其輸入一個參數,小於10則返回true。

std::bind(std::less_equal<int>(), std::placeholders::_1, 10);

  然後組合,即可調用:

using std::placeholders::_1;

auto f = std::bind(std::logical_and<bool>(),

std::bind(std::greater<int>(), std::placeholders::_1, 5),

std::bind(std::less_equal<int>(), std::placeholders::_1, 10));

int count = std::count_if(coll.begin(), coll.end(), f);

C11簡潔之道:函數綁定