std::bind(二):包裝成員函數

前言

關於std::bind()對普通函數的包裝作用,在之前的總結文章《std::bind(一):包裝普通函數》已經舉例說明過了,後來發現丟下了普通函數嵌套包裝的情況,所以在這篇文章中繼續說明一下,然後重點總結std::bind()函數對成員函數的包裝,在面向對象的大潮還未褪去的今天,還是成員函數見到的更多一些,所以講講對它的包裝。

普通函數嵌套包裝

實際上就是普通函數包裝的變形和組合,直接寫個例子吧,如果test1_1()test1_2()test1_3()三個函數的輸出結果都答對了就說明已經掌握了。

void func(int n1, int n2, int n3)
{
    cout << n1 << ' ' << n2 << ' ' << n3 << endl;
}

int calc_value(int c1)
{
    return c1 * c1;
}

void calc_value2(int c1)
{
    int result = c1 * c1;
}

void test1_1()
{
    auto f1 = std::bind(func, placeholders::_1, 101, std::bind(calc_value, placeholders::_2));
    f1(11, 2);   // same as call func(11, 101, calc_value(2))
}

void test1_2()
{
    int n = 2;
    auto f1 = std::bind(func, placeholders::_1, 101, std::bind(calc_value, std::ref(n)));
    n = 4;
    f1(11, 2);   // same as call func(11, 101, calc_value(44)) 多出的參數2無人使用
}

void test1_3()
{
    auto f1 = std::bind(func, placeholders::_1, 101, std::bind(calc_value2, placeholders::_2));
    //f1(11, 2);   // 編譯出錯,無法將參數 3 從“void”轉換爲“int”
}

// 11 101 4
// 11 101 16

第一個test1_1函數的邏輯應該很容易理解,就是把函數calc_value(2)的返回值作爲函數func的第三個參數,而函數test1_2中利用了std::ref()傳遞引用的功能,將變量n作爲引用變量進行傳遞,在包裝調用之前可以感知到參數n的變化。

其實難點在第三個函數test1_3,可能大家知道這裏會報錯,因爲我們需要返回值但是卻包裝了一個沒有返回值的函數,但其實把第二行註釋掉之後,程序就可以成功編譯,也就是說包裝錯誤的函數如果不被調用,是不會報錯的,這一點和模板函類不使用就不會創建很相似,最終是相同的。

包裝類成員

在深入學習std::bind()這個函數之前一直以爲它只能用來包裝函數,後來通過進一步瞭解發現它還能用來包裝成員變量,我們一起來看一下簡單的實現方法。

成員函數的包裝

這裏我們不考慮靜態成員函數,因爲靜態函數沒有this指針,和普通的函數基本一樣,在用法上也沒有很大的差異,所以此處的包裝只考慮成員非靜態函數,可以嘗試分析以下幾個例子。

class CTest
{
public:
    CTest() {}
    ~CTest() {}
public:
    void    func1(int n1, int n2)
    {
        cout << "func1 " << n1 << ' ' << n2 << endl;
    }

    int     n_public;

private:
    void    func2(int n1, int n2)
    {
        cout << "func2 " << n1 << ' ' << n2 << endl;
    }

    int n_private;
};


void test2_1()
{
    CTest testObj;
    auto f2 = std::bind(&CTest::func1, testObj, 101, placeholders::_1);
    f2(1);   // same as call testObj.func1(101, 1)
}

void test2_2()
{
    CTest testObj;
    auto f2 = std::bind(&CTest::func1, &testObj, 101, placeholders::_1);
    f2(2);   // same as call testObj.func1(101, 2)
}

void test2_3()
{
    CTest testObj;
    CTest& obj = testObj;
    auto f2 = std::bind(&CTest::func1, obj, 101, placeholders::_1);
    f2(3);   // same as call testObj.func1(101, 3)
}


void test2_4()
{
    CTest testObj;
    auto f2 = std::bind(&CTest::func1, placeholders::_1, placeholders::_2, 101);
    f2(testObj, 4);   // same as call testObj.func1(4, 101)
}

void test2_5()
{
    CTest testObj;
    // auto f2 = std::bind(&CTest::func2, &testObj, 101, placeholders::_1);
    // 編譯錯誤,func2不可訪問
}

//func1 101 1
//func1 101 2
//func1 101 3
//func1 4 101

前三個函數tes2_1()tes2_2()tes2_3()的作用基本一致,就是將一個類的非靜態成員函數和對象綁定,並且可以動態綁定一些參數,三種調用方式都可以,暫時沒有發現什麼問題,大家知道區別的可以指導我一下,我補充上來,需要注意的是函數std::bind()參數個數需要在原函數參數個數的基礎上加兩個,第一個很明顯就是函數名,而第二個必須是調用這個函數的對象,至於傳遞的是指針還是引用都沒有什麼問題,這兩個參數過後纔是真正的原函數的參數。

函數test2_4()相對於前三個來說更加靈活,將對象也最爲參數在調用時傳入,這就相當於把一個成員函數看成,一個普通函數然後在第一個參數前加this指針的形式,後面這種調用方式在查看C++調用堆棧時應該很容易看到,本質上是一樣,其實這裏還有一個對象傳遞的問題,我們在成員變量時再測試一下。

函數test2_5()出現了編譯錯誤,原因是在使用函數std::bind()的時候也要考慮到原函數的訪問權限,在測試函數中訪問對象的私有函數顯然是不可以的。

成員變量的包裝

void test3_1()
{
    CTest testObj;
    auto f3 = std::bind(&CTest::n_public, testObj);
    f3(1) = 10;
    cout << f3(1) << endl;
    cout << testObj.n_public << endl;
}


void test3_2()
{
    CTest testObj;
    auto f4 = std::bind(&CTest::n_public, placeholders::_1);
    f4(testObj) = 4;
    cout << f4(testObj) << endl;
    cout << testObj.n_public << endl;
}


void test3_3()
{
    CTest testObj;
    auto f3 = std::bind(&CTest::n_public, std::ref(testObj));
    f3(1) = 11;
    cout << f3(1) << endl;
    cout << testObj.n_public << endl;
}

//10
//-858993460
//4
//4
//11
//11

這個成員變量的綁定測試結果,有沒有讓人意想不到呢?或者說這種f3(1) = 10;寫法已經讓人很驚訝了,其實我在寫例子的時候就是簡單試試,沒想到這樣寫居然可以,看起來好像把一個值賦值給了一個函數一樣。

函數test3_1()的第二個輸出可能有點想不到,但是看到結果是有些人可能就明白了,因爲在上一篇裏提到“std::bind()函數中的參數在被複制或者移動時絕不會以引用的方式傳遞,除非你使用了std::ref()或者std::cref()包裝的參數”。

因爲沒有使用std::ref()函數包裝,所以std::bind()函數綁定的testObj對象實際上是原對象的副本,那麼針對於副本的操作和修改自然就不會反應到原對象上,這也就是打印testObj.n_public會輸出隨機值的原因。

函數test3_2()在綁定時並沒有具體到特定的對象,而是使用了placeholders::_1佔位符,這樣生成的函數,在調用的時候再傳入操作對象,那麼此時修改對象屬性就可以起作用了。

函數test3_3()是針對於函數test3_1()的,添加了std::cref()包裝的原對象,可以通過綁定後的函數修改。

總結

  1. std::bind()函數可以嵌套綁定
  2. std::bind()函數綁定成員函數時,函數名參數後面需要緊跟着類的對象作爲參數
  3. std::bind()不僅可以綁定普通函數、成員函數、還可以綁定成員變量

完整代碼

代碼傳送門

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