C++的回調技術 std::bind+std::function

參考自《Linux多線程服務端編程》以及muduo源碼,對其中的一些實現細節有着十分深刻的印象,尤其是使用std::bindstd::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

 

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