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)可供使用。什麼?嫌太少?你見過有函式一次性呼叫二三十個引數的嗎?
繫結成員函式
- 第一個引數為物件的成員函式指標,且由於其不支援類成員函式隱式轉換為函式指標,所以需要手動加上取值符&
- 使用物件成員函式的指標時,需要指定該指標屬於那個物件。所以需要創建出一個物件例項並將它的地址當作引數
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
繫結引用引數
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
總結
std::bind
預先繫結的引數需要傳遞具體變數或者值進去,同時此過程是pass-by-value,如果想以pass-by-reference的形式進行傳遞,則需要使用std::ref
或是std::cref
- 若不想預先傳值,則需要傳入佔位符,從
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::function
和std::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
摸了,下次一定