參考自《Linux多線程服務端編程》以及muduo源碼,對其中的一些實現細節有着十分深刻的印象,尤其是使用std::bind和std::function的回調技術。可以說,這兩個大殺器簡直就是現代C++的“任督二脈”,甚至可以解決繼承時的虛函數指代不清的問題。在此詳細敘述使用std::bind和std::function在C++對象之間的用法,用以配合解決事件驅動的編程模型。
本文組成:
1.std::function
2.std::bind
3.使用std::bind和std::function的回調技術
4.std::bind綁定到虛函數時會表現出多態行爲,解決繼承時的虛函數指代不清的問題
function模板類和bind模板函數,使用它們可以實現類似函數指針的功能,但卻卻比函數指針更加靈活,特別是函數指向類 的非靜態成員函數時。不過網上有文章簡單測試:函數指針要比直接調用慢2s左右;std::function 要比函數指針慢2s左右 。該測試結果鏈接:https://cherishlc.iteye.com/blog/2002095
std::function
std::function包含於頭文件 #include<functional>中,可將各種可調用實體進行封裝統一,包括
1.普通函數
2.lambda表達式
3.函數指針
4.仿函數(functor 重載括號運算符實現)
5.類成員函數
6.靜態成員函數
下面source.cpp實例通過上述幾種方式實現一個簡單的比較兩個數的大小的功能(讀者可拷貝代碼觀察結果):
#include <iostream>
#include <functional>
using namespace std;
std::function<bool(int, int)> fun;
//1.普通函數
bool compare_com(int a, int b)
{
return a > b;
}
//2.lambda表達式
auto compare_lambda = [](int a, int b){ return a > b;};
//3.仿函數
class compare_class
{
public:
bool operator()(int a, int b)
{
return a > b;
}
};
//4.類成員函數(動態、靜態)
class compare
{
public:
bool compare_member(int a, int b)
{
return a > b;
}
static bool compare_static_member(int a, int b)
{
return a > b;
}
};
//對應的main函數如下:
int main()
{
bool result;
fun = compare_com;
result = fun(10, 1);
cout << "普通函數輸出, result is " << result << endl;
fun = compare_lambda;
result = fun(10, 1);
cout << "lambda表達式輸出, result is " << result << endl;
fun = compare_class();
result = fun(10, 1);
cout << "仿函數輸出, result is " << result << endl;
fun = compare::compare_static_member;
result = fun(10, 1);
cout << "類靜態成員函數輸出, result is " << result << endl;
compare temp;
fun = std::bind(&compare::compare_member, temp, std::placeholders::_1, std::placeholders::_2);
result = fun(10, 1);
cout << "類普通成員函數輸出, result is " << result << endl;
}
std::bind
std::bind函數將可調用對象(開頭所述6類)和可調用對象的參數進行綁定,返回新的可調用對象(std::function類型,參數列表可能改變),返回的新的std::function可調用對象的參數列表根據bind函數實參中std::placeholders::_x從小到大對應的參數確定。
下面source2.cpp實例詳細說明返回的新的std::function可調用對象的參數列表如何確定:
#include <functional>
#include <iostream>
using namespace std;
struct Int
{
int a;
};
bool compare_com(struct Int a, float b)
{
return a.a > b;
}
int main()
{
Int a = {1};
//placeholders::_1對應float, placeholders::_2對應struct Int所以返回值fun的類型爲 function<bool(float, Int)>
std::function<bool(float, struct Int)> fun = bind(compare_com, placeholders::_2, placeholders::_1);
bool result = fun(2.0, a);
cout << "result is " << result << endl;
return 0;
}
使用std::bind和std::function的回調技術
C++的類成員函數不能像普通函數那樣用於回調,因爲每個成員函數都需要有一個對象實例去調用它。
通常情況下,要實現成員函數作爲回調函數:
一種過去常用的方法就是把該成員函數設計爲靜態成員函數(因爲類的成員函數需要隱含的this指針 而回調函數沒有辦法提供),但這樣做有一個缺點,就是會破壞類的結構性,因爲靜態成員函數只能訪問該類的靜態成員變量和靜態成員函數,不能訪問非靜態的,要解決這個問題,可以把對象實例的指針或引用做爲參數傳給它。後面就可以靠這個對象實例的指針或引用訪問非靜態成員函數。
另一種辦法就是使用std::bind和std::function結合實現回調技術。
下面的所有討論基於對象。
#include<iostream>
#include<functional>
typedef std::function<void()> Functor;
class Blas
{
public:
virtual void add(int a, int b)
{
std::cout << a + b << std::endl;
}
static void addStatic(int a, int b)
{
std::cout << a + b << "this Blas" << std::endl;
}
};
class BBlas : public Blas
{
virtual void add(int a, int b)
{
std::cout << a + b << "this BBlas" <<std::endl;
}
};
int main()
{
//Blas blas;
BBlas blas;
//使用bind綁定類靜態成員函數
Functor functor(std::bind(&Blas::addStatic, 1, 2));
functor();
//使用bind綁定類的chengyuan函數
Functor functor2(std::bind(&Blas::add, blas, 1, 2));
functor2();
return 0;
}
結果
3this Blas
3this BBlas
上述代碼中的區別是:如果不是類的靜態成員函數,需要在參數綁定時,往綁定的參數列表中加入使用的對象。
使用function和bind實現回調功能
#include<iostream>
#include<functional>
typedef std::function<void()> Functor;
class Blas
{
public:
void setCallBack(const Functor& cb)
{functor = cb;};
void printFunctor()
{functor();};
private:
Functor functor;
};
class Atlas
{
public:
Atlas(int x_) : x(x_)
{
//使用當前類的靜態成員函數
blas.setCallBack(std::bind(&addStatic,x,2));
//使用當前類的非靜態成員函數
blas.setCallBack(std::bind(&Atlas::add,this,x,2));
}
void print()
{
blas.printFunctor();
}
private:
void add(int a,int b)
{
std::cout << a+b << std::endl;
}
static void addStatic(int a,int b)
{
std::cout << a+b << std::endl;
}
Blas blas;
int x;
};
int main(int argc,char** argv)
{
Atlas atlas(5);
atlas.print();
return 0;
}
兩個函數在Atlas類中,並且可以自由操作Atlas的數據成員。儘管是將add()系列的函數封裝成函數對象傳入Blas中,並且在Blas類中調用,但是它們仍然具有操作Atlas數據成員的功能,在兩個類之間形成了弱的耦合作用。但是如果要在兩個類之間形成弱的耦合作用,必須在使用std::bind()封裝時,向其中傳入this指針:
C++中 直接調用、函數指針、std::function效率對比
https://www.iteye.com/blog/cherishlc-2002095