C++——包裝器std::function與綁定器std::bind
1、可調用對象的包裝器
std::function是可調用對象的包裝器。它是一個
類模闆
,
可以容納除了類成員(函數)指針之外的所有可調用對象
。
通過指定它的模闆參數,它可以用統一的方式處理函數、函數對象、函數指針,
并允許儲存和延遲執行它們。
1.1基本用法
使用std::function必須包含一個頭檔案叫做functional,具體文法如下
#include <functional>
std::function<傳回值類型(參數類型清單)> name = 可調用對象;
示例:
#include <iostream>
#include <functional>
using namespace std;
int add(int a, int b)
{
cout << a << " + " << b << " = " << a + b << endl;
return a + b;
}
class T1
{
public:
static int sub(int a, int b)
{
cout << a << " - " << b << " = " << a - b << endl;
return a - b;
}
int operator()(int a, int b)
{
cout << a << " * " << b << " = " << a * b << endl;
return a * b;
}
};
class T2
{
public:
int operator()(int a, int b)
{
cout << a << " * " << b << " = " << a * b << endl;
return a * b;
}
double operator()(int a, double b)
{
cout << a << " + " << b << " = " << a + b << endl;
return a + b;
}
};
int main(void)
{
// 綁定一個普通函數
function<int(int, int)> f1 = add;
// 綁定以靜态類成員函數
T1 c;
function<int(int, int)> f2 = T1::sub;
function<int(int, int)> f4 = c;//可自主識别對象裡的仿函數
// 綁定一個仿函數
T2 t;
function<double(int, double)> f3 = t;//可自主識别對象裡的仿函數
// 函數調用
f1(9, 3);
f2(9, 3);
f4(9, 3);
f3(9, 3);
return 0;
}

可自主識别對象裡的仿函數,
通過測試代碼可以得到結論:std::function 可以将可調用對象進行包裝,得到一個統一的格式,包裝完成得到的對象相當于一個函數指針,和函數指針的使用方式相同,通過包裝器對象就可以完成對包裝的函數的調用了。
1.2作為回調函數使用
#include <iostream>
#include <functional>
using namespace std;
class A
{
public:
// 構造函數參數是一個包裝器對象
A(const function<void()>& f) : callback(f)
{
}
void notify()
{
callback(); // 調用通過構造函數得到的函數指針
}
private:
function<void()> callback;
};
class B
{
public:
void operator()()
{
cout << "sssssssssssssssssss!!!" << endl;
}
};
int main(void)
{
B b;
A a(b); // 仿函數通過包裝器對象進行包裝
a.notify();
return 0;
}
通過上一個示例得到,
A a(b);
可自動識别出仿函數并包裝作為函數參數,最終輸出如下:
通過上面的例子可以看出,使用對象包裝器 std::function 可以非常友善的将仿函數轉換為一個函數指針,通過進行函數指針的傳遞,在其他函數的合适的位置就可以調用這個包裝好的仿函數了。
另外,使用 std::function 作為函數的傳入參數,可以将定義方式不相同的可調用對象進行統一的傳遞,這樣大大增加了程式的靈活性。
2、 綁定器
std::bind用來将可調用對象與其參數一起進行綁定。綁定後的結果可以使用std::function進行儲存,并延遲調用到任何我們需要的時候。通俗來講,它主要有兩大作用:
将可調用對象與其參數一起綁定成一個仿函數。
将多元(參數個數為n,n>1)可調用對象轉換為一進制或者(n-1)元可調用對象,即隻綁定部分參數。
綁定器函數使用文法格式如下:
// 綁定非類成員函數/變量
auto f = std::bind(可調用對象位址, 綁定的參數/占位符);
// 綁定類成員函/變量
auto f = std::bind(類函數/成員位址, 類執行個體對象位址, 綁定的參數/占位符);
示例:
在下面的程式中,使用了 std::bind 綁定器,在函數外部通過綁定不同的函數,控制了最後執行的結果。std::bind綁定器傳回的是一個仿函數類型,得到的傳回值可以直接指派給一個std::function,在使用的時候我們并不需要關心綁定器的傳回值類型,使用auto進行自動類型推導就可以了。
placeholders::\_1 是一個占位符,代表這個位置将在函數調用時被傳入的第一個參數所替代。同樣還有其他的占位符 placeholders::\_2、placeholders::\_3、placeholders::\_4、placeholders::\_5 等……
#include <iostream>
#include <functional>
using namespace std;
void callFunc(int x, const function<void(int)>& f)
{
if (x % 2 == 0)
{
f(x);
}
}
void output(int x)
{
cout << x << " ";
}
void output_add(int x)
{
cout << x + 10 << " ";
}
int main(void)
{
// 使用綁定器綁定可調用對象和參數
auto f1 = bind(output, placeholders::_1);
for (int i = 0; i < 10; ++i)
{
callFunc(i, f1);
}
cout << endl;
auto f2 = bind(output_add, placeholders::_1);
for (int i = 0; i < 10; ++i)
{
callFunc(i, f2);
}
cout << endl;
return 0;
}
再看示例:
#include <iostream>
#include <functional>
using namespace std;
void output(int x, int y)
{
cout << x << " " << y << endl;
}
int main(void)
{
// 使用綁定器綁定可調用對象和參數, 并調用得到的仿函數
bind(output, 1, 2)();
bind(output, placeholders::_1, 2)(10);
bind(output, 2, placeholders::_1)(10);
// error, 調用時沒有第二個參數
// bind(output, 2, placeholders::_2)(10);
// 調用時第一個參數10被吞掉了,沒有被使用
bind(output, 2, placeholders::_2)(10, 20);
bind(output, placeholders::_1, placeholders::_2)(10, 20);
bind(output, placeholders::_2, placeholders::_1)(10, 20);
return 0;
}
解釋:
1 2 | bind(output, 1, 2)(); |
---|---|
10 2 | bind(output, placeholders::_1, 2)(10); |
2 10 | bind(output, 2, placeholders::_1)(10); |
2 20 | bind(output, 2, placeholders::_2)(10, 20); |
10 20 | bind(output, placeholders::_1, placeholders::_2)(10, 20); |
20 10 | bind(output, placeholders::_2, placeholders::_1)(10, 20); |
通過測試可以看到,std::bind 可以直接綁定函數的所有參數,也可以僅綁定部分參數。在綁定部分參數的時候,通過使用 std::placeholders 來決定空位參數将會屬于調用發生時的第幾個參數。
可調用對象包裝器 std::function 是不能實作對類成員函數指針或者類成員指針的包裝的,但是通過綁定器 std::bind 的配合之後,就可以完美的解決這個問題了,再來看一個例子,然後再解釋裡邊的細節:
#include <iostream>
#include <functional>
using namespace std;
class Test
{
public:
void output(int x, int y)
{
cout << "x: " << x << ", y: " << y << endl;
}
int m_number = 100;
};
int main(void)
{
Test t;
// 綁定類成員函數
function<void(int, int)> f1 =
bind(&Test::output, &t, placeholders::_1, placeholders::_2);
// 綁定類成員變量(公共)
function<int&(void)> f2 = bind(&Test::m_number, &t);
// 調用
f1(520, 1314);
f2() = 2333;
cout << "t.m_number: " << t.m_number << endl;
return 0;
}
在用綁定器綁定類成員函數或者成員變量的時候需要将它們所屬的執行個體對象一并傳遞到綁定器函數内部。
f1的類型是function<void(int, int)>,通過使用std::bind将Test的成員函數output的位址和對象t綁定,并轉化為一個仿函數并存儲到對象f1中。
使用綁定器綁定的類成員變量m_number得到的仿函數被存儲到了類型為function<int&(void)>的包裝器對象f2中,并且可以在需要的時候修改這個成員。其中int是綁定的類成員的類型,并且允許修改綁定的變量,是以需要指定為變量的引用,由于沒有參數是以參數清單指定為void。
示例程式中是使用 function 包裝器儲存了 bind 傳回的仿函數,如果不知道包裝器的模闆類型如何指定,可以直接使用 auto 進行類型的自動推導,這樣使用起來會更容易一些。
參考:C++ prime plus 第六版
愛程式設計的大丙