C++ 11中的std::bind和std::function


C++11中的std::bind和std::function

可調用對象

  • 是一個函數指針
  • 一個類成員函數指針
  • 可被轉換成函數指針的類對象
  • 是一個具有operator()成員函數的類的對象

std::bind

std::bind可以理解為“綁定”

綁定普通函數,綁定靜態普通函數

int AddFunc(int a, int b) { return a + b; }
auto FuncBind = std::bind(AddFunc, 2, 3);	//AddFunc隱式轉換為一個函數指針
std::cout << FuncBind() << std::endl;		// 5

函數AddFunc被綁定到了FuncBind,而它的參數綁定到一個具體的值上,這個例子中綁定到了2和3,因此在函數調用的時候程序給出的結果是5

參數還可以綁定到占位符(std::placeholders)上,在調用FuncBind時再給出其參數值

auto FuncBind = std::bind(AddFunc, std::placeholders::_1, 3);
std::cout << FuncBind(5) << std::endl;		// 8

占位符,顧名思義,它替“5”占了一個 位子,后續調用時,“5”來了,坐了別人給他占的座位。你當然也可以給“3”提供一個占位符

std::bind(AddFunc, std::placeholders::_1, std::placeholders::_2);
FuncBind(6,9);		//15

C++提供了多達20個占位符(_1, _20)可供使用。什么?嫌太少?你見過有函數一次性調用二三十個參數的嗎?

靜態普通函數與普通函數同理,不再贅述

綁定成員函數

  1. 第一個參數為對象的成員函數指針,且由於其不支持類成員函數隱式轉換為函數指針,所以需要手動加上取值符&
  2. 使用對象成員函數的指針時,需要指定該指針屬於那個對象。所以需要創建出一個對象實例並將它的地址當作參數
class MyClass {
public:
    int Sum(int a, int b) { return a + b; }
};
MyClass myc;
auto FuncBind = std::bind(&MyClass::Sum, &myc, 10, 20);
//調用FuncBind的結果為:30

綁定靜態成員函數

與成員函數不同,你不再需要提供一個對象的指針了

class MyClass {
public:
    static int Sum(int a, int b) { return a + b; }
};
MyClass myc;
auto FuncBind = std::bind(&MyClass::Sum, 10, 20);
//調用FuncBind的結果為:30

綁定模板函數

綁定引用參數

std::ref用於包裝按引用傳遞的值

std::cref用於包裝按常量引用傳遞的值

int Sum(int& a, const int& b) { return a++ + b; }
int a = 1, b = 2;
auto FuncBind = std::bind(Sum, std::ref(a), std::cref(b));		//運行后,a = 2, b = 2, 結果為3

總結

  1. std::bind預先綁定的參數需要傳遞具體變量或者值進去,同時此過程是pass-by-value,如果想以pass-by-reference的形式進行傳遞,則需要使用std::ref或是std::cref
  2. 若不想預先傳值,則需要傳入占位符,從std::placeholders_1開始,逐步遞增,此過程為pass-by-reference

std::function

封裝函數

std::function是一種通用,多態的函數封裝。可容納各種可調用對象,例如普通函數,函數指針,Lambda表達式以及std::bind表達式等。換句話說,可以當作是函數的容器。對於類型不安全的函數指針來說,將其封裝成一個安全的std::function對象是一個良好的選擇

假設有一個簡單的普通函數

int AddFunc(int a, int, b) { return a + b; }

利用std::function進行封裝

int (*MyFuncP)(int, int) = AddFunc;					//使用函數指針
std::function<int(int, int)> MyFunc = AddFunc;			//利用std::function進行封裝

與std::bind配合使用

像是封裝函數對象(其實就是operator()),Lambda表達式等其他可調用對象的過程與上述類似,不再贅述,重點關注std::functionstd::bind搭配使用

class MyClass {
public:
    int Sum(int a, int b) { return a + b; }
    int num;
};
using namespace std::placeholders;
MyClass myc;
std::function<int(int, int)> MyFunc = std::bind(&MyClass::Sum, &myc, _1, _2);	//綁定成員函數
std::function<int&()> MyValue = std::bind(&MyClass::num, &myc);					//綁定成員變量
MyValue() = 100;			//等效於 myc.num = 100;

與函數指針對比

使用std::function+std::bind

class Base
{
public:
    void Called() {
        for (int i = 0; i < myVec.size(); i++)
            myVec[i]();
    }
protected:
    std::vector<std::function<void()>> myVec;
};
class Devired : public Base
{
public:
    Devired()
    {
        std::function<void()> st = std::bind(&Devired::DoSomething, this);
        std::function<void()> et = std::bind(&Devired::DoEverything, this);
        myVec.emplace_back(st);
        myVec.emplace_back(et);
    }
private:
    void DoSomething() { std::cout << "Some\n"; }
    void DoEverything() { std::cout << "Every\n"; }
};

在主函數中通過基類中的函數以及成員調用到派生類的方法

Devired dr;
dr.Called();		//控制台輸出 Some Every

使用函數指針

class BaseP
{
public:
    void Called() {
        for (int i = 0; i < myVec.size(); i++)
            myVec[i]();
    }
protected:
    std::vector<void(*)()> myVec;
};

在基類中將創建一個裝 void返回值的無參函數指針 的容器,然后在派生類的構造函數中將派生類成員函數加進容器中

//省略其余部分
DeviredP()
{
    auto st = &DeviredP::DoSomething;
    auto et = &DeviredP::DoEverything;
    myVec.emplace_back(st);
    myVec.emplace_back(et);
}

這種寫法是錯誤的,調用drp.Called()時會報錯

派生類

class BaseP { virtual void Called() = 0; };
class DeviredP : public BaseP
{
public:
    DeviredP()
    {
        auto st = &DeviredP::DoSomething;
        auto et = &DeviredP::DoEverything;
        myVec.emplace_back(st);
        myVec.emplace_back(et);
    }
    void Called() override
    {
        for (int i = 0; i < myVec.size(); i++)
            (this->*myVec[i])();				//需要用this指針指明
    }
private:
    std::vector<void(DeviredP::*)()> myVec;			//需指定類名:儲存的是DeviredP類的成員函數指針
    void DoSomething() { std::cout << "Some\n"; }
    void DoEverything() { std::cout << "Every\n"; }
};

不僅儲存指針的時候變繁瑣了,調用時還應使用this顯示指明。而且還需要在每個不同的派生類間去書寫相同的代碼,項目變得冗長。

使用函數指針的版本無法在基類中取到派生類的函數指針,調用邏輯也需要在子類中去實現。而使用std::function,函數都被統一歸為function函數對象,方便基類調用

漫漫談

虛函數與std::function

虛函數的方法實現責任鏈模式

struct Request { int RequestType; };
class Handler
{
public:
    void setNext(std::shared_ptr<Handler> shrd) { nextHandler = std::move(shrd); }
    virtual void HandlerRequest(Request rq)
    {
        if (nextHandler)
            nextHandler->HandlerRequest(rq);
        else
            std::cout << "Cant Handle\n";
    }
protected:
    std::shared_ptr<Handler> nextHandler;
};
class DeviredHandler1 : public Handler
{
public:
    void HandlerRequest(Request rq) override
    {
        if (rq.RequestType == 1)
            std::cout << "Handle by 1\n";
        else
            Handler::HandlerRequest(rq);
    }
};
// DeviredHandler2,DeviredHandler3...
Request r = { 3 };
auto d1 = std::make_shared<DeviredHandler1>();
auto d2 = std::make_shared<DeviredHandler2>();
auto d3 = std::make_shared<DeviredHandler3>();
d1->setNext(d2);
d2->setNext(d3);
d1->HandlerRequest(r);		// Handle by 3

std::bind + std::function方式實現責任鏈模式

在原有的基礎上增加ChainHandler

using MyFunc = std::function<void(Request)>;
class ChainHandler
{
public:
    MyFunc Myfunc;
    void HandleRequest(Request rq) { Myfunc(rq); }
    void Assemble(MyFunc call, MyFunc next, Request rq)
    {
        if (next != nullptr)
            next(rq);
        else
            call(rq);
    }
};
//main函數中不再使用 d1->HandlerRequest(r);
ChainHandler chain;
MyFunc m1 = std::bind(&DeviredHandler1::HandlerRequest, d1, std::placeholders::_1);
MyFunc m2 = std::bind(&DeviredHandler2::HandlerRequest, d2, std::placeholders::_1);
MyFunc m3 = std::bind(&DeviredHandler3::HandlerRequest, d3, std::placeholders::_1);
chain.Myfunc = std::bind(&ChainHandler::Assemble, &chain, m1, chain.Myfunc, std::placeholders::_1);
chain.Myfunc = std::bind(&ChainHandler::Assemble, &chain, m2, chain.Myfunc, std::placeholders::_1);
chain.Myfunc = std::bind(&ChainHandler::Assemble, &chain, m3, chain.Myfunc, std::placeholders::_1);
chain.HandleRequest(r);		// Handle by 3

使用std::function創建一個自動注冊工廠

摸了,下次一定

C++11實現一個自動注冊的工廠

一個更好的自動注冊工廠

再談自動注冊的工廠


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM