C++ std::bind函數適配器

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

參考博客:https://www.jianshu.com/p/f191e88dcc80

參考博客:https://www.cnblogs.com/sick-vld/p/10769187.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章