0.初識
C++11 提供了一個名爲std::bind的函數模板,可用於生成可調用對象的轉發調用包裝器,相當於是個通用函數適配器(舊版本的bind1st/bind2nd已被棄用),可以適配任意的可調用對象,包括函數指針、函數引用、成員函數指針和函數對象。它接受一個可調用對象,生成一個新的可調用對象來適配原來的參數列表。
bind將可調用對象與其參數一起進行綁定,綁定後的結果可以使用std::function保存。bind綁定完成後,返回一個函數對象,它內部保存了原可調用對象的拷貝,具有operator(),返回值類型被自動推導爲原可調用對象的返回值類型。調用時,這個函數對象將把之前存儲的參數轉發給原可調用對象完成調用。
1.綁定普通函數
一般形式如下:
auto newCallable = std::bind(callable, arg_list);
第一個參數爲要綁定的可調用對象,後跟參數列表(參數列表同可調用對象的參數列表匹配)。參數列表中可包含名字形如 _n (n爲整數)的佔位符,n表示生成的可調用對象中參數的位置。佔位符位於std::placeholders命名空間。
#include <iostream>
#include <functional>
//相加
int add(int a, int b)
{
std::cout << "a:" << a << "\tb:" << b << std::endl;
return a + b;
}
void test_bind()
{
//第一個參數是函數名,普通函數做實參時,會隱式轉換成函數指針,相當於&add
//std::placeholders::_1佔位符相當於保留了第一個參數
auto func_add_10 = std::bind(add, std::placeholders::_1, 10);
int result = func_add_10(100); //等於100+10
std::cout << "func_add_10(100):" << result << std::endl;
//對參數重排序
using namespace std::placeholders;
auto func_add = std::bind(add, _2, _1); //參數位置對換了
result = func_add(100, 10);
std::cout << "func_add(100,10):" << result << std::endl;
}
輸出:
2.綁定引用參數
bind會拷貝其參數,如果我們傳遞給bind一個對象而又不拷貝他,就必須使用標準庫的red函數。ref函數返回一個對象,包含給定的引用,此對象是可以拷貝的。標準庫中還有一個cref函數,返回一個保存const引用的對象。
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>
#include <sstream>
using namespace std::placeholders;
using namespace std;
ostream& print(ostream& os, const string& s, char c)
{
os << s << c;
return os;
}
int main()
{
vector<string> words{ "helo", "world", "this", "is", "C++11" };
ostringstream os;
char c = ' ';
for_each(words.begin(), words.end(),
[&os, c](const string& s) {os << s << c; });
cout << os.str() << endl;
ostringstream os1;
// ostream不能拷貝,若希望傳遞給bind一個對象,
// 而不拷貝它,就必須使用標準庫提供的ref函數
for_each(words.begin(), words.end(),
bind(print, ref(os1), _1, c));
cout << os1.str() << endl;
system("pause");
return 0;
}
3.綁定類成員函數
bind綁定類成員函數時,第一個參數表示對象的成員函數的指針,第二個參數表示對象的地址。
struct Foo {
void print_sum(int n1, int n2)
{
std::cout << n1+n2 << '\n';
}
int data = 10;
};
int main()
{
Foo foo;
auto f = std::bind(&Foo::print_sum, &foo, 95, std::placeholders::_1);
f(5); // 100
}
注意:必須顯示的指定&Foo::print_sum,因爲編譯器不會將對象的成員函數隱式轉換成函數指針,所以必須在Foo::print_sum前添加&取地址符;使用對象成員函數的指針時,必須要知道該指針屬於哪個對象,因此第二個參數爲對象的地址 &foo;
4.其他示例
示例來自在線手冊:
#include <random>
#include <iostream>
#include <memory>
#include <functional>
void f(int n1, int n2, int n3, const int& n4, int n5)
{
std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}
int g(int n1)
{
return n1;
}
struct Foo {
void print_sum(int n1, int n2)
{
std::cout << n1 + n2 << '\n';
}
int data = 10;
};
int main()
{
using namespace std::placeholders; // 對於 _1, _2, _3...
// 演示參數重排序和按引用傳遞
int n = 7;
// ( _1 與 _2 來自 std::placeholders ,並表示將來會傳遞給 f1 的參數)
auto f1 = std::bind(f, _2, 42, _1, std::cref(n), n);
n = 10;
f1(1, 2, 1001); // 1 爲 _1 所綁定, 2 爲 _2 所綁定,不使用 1001
// 進行到 f(2, 42, 1, n, 7) 的調用
// 嵌套 bind 子表達式共享佔位符
auto f2 = std::bind(f, _3, std::bind(g, _3), _3, 4, 5);
f2(10, 11, 12); // 進行到 f(12, g(12), 12, 4, 5); 的調用
// 常見使用情況:以分佈綁定 RNG
std::default_random_engine e;
std::uniform_int_distribution<> d(0, 10);
std::function<int()> rnd = std::bind(d, e); // e 的一個副本存儲於 rnd
for (int n = 0; n < 10; ++n)
std::cout << rnd() << ' ';
std::cout << '\n';
// 綁定指向成員函數指針
Foo foo;
auto f3 = std::bind(&Foo::print_sum, &foo, 95, _1);
f3(5);
// 綁定指向數據成員指針
auto f4 = std::bind(&Foo::data, _1);
std::cout << f4(foo) << '\n';
// 智能指針亦能用於調用被引用對象的成員
std::cout << f4(std::make_shared<Foo>(foo)) << '\n'
<< f4(std::make_unique<Foo>(foo)) << '\n';
system("pause");
return 0;
}
輸出:
5.參考
參考書籍:《C++ Primer》中文第五版
參考文檔:https://zh.cppreference.com/w/cpp/utility/functional/bind