《深入應用C++11》筆記-std::function和bind
接下來的內容將會補充在《深入理解C++11》書中沒有涉及的一些比較常用的特性,內容取自《深入應用C++11》。
在C++中,存在“可呼叫物件(Callable Objects)”這麼一個概念。準確來說,可呼叫物件有如下幾種定義:
- 是一個函式指標。
- 是一個具有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 a_;
void mem_func(void)
{
}
};
int main(void)
{
void(*func_ptr)(void) = &func; // 1. 函式指標
func_ptr();
Foo foo; // 2. 仿函式
foo();
Bar bar; // 3. 可被轉換為函式指標的類物件
bar();
void (A::*mem_func_ptr)(void) // 4. 類成員函式指標
= &A::mem_func;
int A::*mem_obj_ptr // 或者是類成員指標
= &A::a_;
A aa;
(aa.*mem_func_ptr)();
aa.*mem_obj_ptr = 123;
return 0 ;
}
上面對可呼叫型別的定義裡並沒有包括函式型別或者函式引用(只有函式指標)。這是因為函式型別並不能直接用來定義物件;而函式引用從某種意義上來說,可以看做一個const的函式指標。C++中的可呼叫物件雖然具有比較統一的操作形式(除了類成員指標之外,都是後面加括號進行呼叫),但定義方法五花八門。這樣在我們試圖使用統一的方式儲存,或傳遞一個可呼叫物件時,會十分煩瑣。現在,C++11通過提供std::function和std::bind統一了可呼叫物件的各種操作。
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(); // func
// 繫結一個類的靜態成員函式
std::function<int(int)> fr2 = Foo::foo_func;
std::cout << fr2(123) << std::endl; // foo_func(123) ->: 123
Bar bar;
fr2 = bar; // 繫結一個仿函式
std::cout << fr2(123) << std::endl; // operator()(123) ->: 123
return 0;
}
從上面我們可以看到std::function的使用方法,當我們給std::function填入合適的函式簽名(即一個函式型別,只需要包括返回值和引數表)之後,它就變成了一個可以容納所有這一類呼叫方式的“函式包裝器”。
另外,std::function還能作為函式的入參以及函式指標:
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有個好處就是能夠將物件的成員函式作為引數,如果使用自定義的函式指標則會報錯:
using pfunc = void(*)();
class A
{
pfunc callback_;
public:
A(pfunc 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::bind
std::bind用來將可呼叫物件與其引數一起進行繫結。繫結後的結果可以使用std::function進行儲存,並延遲呼叫到任何我們需要的時候。它主要有兩大作用:
- 將可呼叫物件與其引數一起繫結成一個仿函式。
- 將多元(引數個數為n,n>1)可呼叫物件轉成一元或者(n-1)元可呼叫物件,即只繫結部分引數。
void call_when_even(int x, const std::function<void(int)>& f)
{
if (x % 2 == 0)
{
f(x);
}
}
void output(int x)
{
std::cout << x << " ";
}
void output_add_2(int x)
{
std::cout << x + 2 << " ";
}
int main(void)
{
auto func= std::bind(output, std::placeholders::_1);
for (int i = 0; i < 10; ++i)
{
call_when_even(i, func);
}
std::cout << std::endl; // 0 2 4 6 8
func= std::bind(output_add_2, std::placeholders::_1);
for (int i = 0; i < 10; ++i)
{
call_when_even(i, func);
}
std::cout << std::endl;
return 0; // 2 4 6 8 10
}
同樣還是上面std::function中最後的一個例子,只是在這裡,我們使用了std::bind,在函式外部通過繫結不同的函式,控制了最後的執行結果。我們使用auto型別儲存std::bind的返回結果,是因為我們並不關心std::bind真正的返回型別(實際上std::bind的返回型別是一個stl內部定義的仿函式型別),只需要知道它是一個仿函式,可以直接賦值給一個std::function。當然,這裡直接使用std::function型別來儲存std::bind的返回值也是可以的。std::placeholders::_1是一個佔位符,代表這個位置將在函式呼叫時,被傳入的第一個引數所替代。可以通過以下的例子觀察怎麼使用佔位符:
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); // 編譯失敗,呼叫時沒有第二個引數
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
bind還有一個強大之處就是可以組合多個函式。假設要找出集合中大於5小於10的元素個數應該怎麼做呢?
int main(void)
{
std::vector<int> arr = { 0,1,2,3,4,5,6,7,8,9 };
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(arr.begin(), arr.end(), f);
std::cout << count << std::endl;
return 0;
}
以上程式碼中,std::logical_and<bool>()
是一個模板結構體成員函式,作用是返回傳入的兩個引數的邏輯與結果,例如:
std::logical_and<bool>()(1, 2); // 返回1&&2的結果
std::logical_and<bool>()
通過bind綁定了std::greater<int>()
和std::less_equal<int>()
,就能得到他們兩個返回值的邏輯與結果。而std::greater<int>()
和std::less_equal<int>()
也是模板結構體成員函式,第一個引數是被比較值,綁定了第一個入參,第二引數是比較值,通過兩個引數比較返回bool值。接著講bind返回的函式,傳入std::count_if,它的作用是遍歷指定的一組資料並傳入指定函式,函式返回true則count+1。這樣就能得到集合中大於5小於10的元素個數了。
以上例子只是為了說明bind組合函式的方法,實際上用lambda函式實現更為簡單:
int main(void)
{
std::vector<int> arr = { 0,1,2,3,4,5,6,7,8,9 };
int count = std::count_if(arr.begin(), arr.end(), [](int x) {
return x > 5 && x < 10;
});
std::cout << count << std::endl;
return 0;
}