轉(zhuǎn)自:高性能架構(gòu)探索
自從在使用?std::thread?構(gòu)造函數(shù)過(guò)程中遇到了?Callable?類型的概念以來(lái)用到了很多關(guān)于它的使用.
因此本文把使用/調(diào)查結(jié)果總結(jié)出來(lái). 包括 Callable 的基礎(chǔ)概念, 典型的?Callable?類型介紹.
例如函數(shù)對(duì)象(狹義), 函數(shù)指針, lambda 匿名函數(shù), 函數(shù)適配器,?std::function?仿函數(shù)等.
Callable 類型
基礎(chǔ)
??定義(參考):可調(diào)用(Callable)?類型是可應(yīng)用?INVOKE?操作(std::invoke?是在 C++17 里定義的類, 感覺意思就是執(zhí)行函數(shù)操作的模板類.)
??要求:一個(gè)?T?類型要滿足為 callable 需要以下表達(dá)式在不求值語(yǔ)境中良構(gòu).INVOKE
??詳細(xì)地
1.?若?f?是類?T?的成員函數(shù)指針: 上面等價(jià)于?(t1.*f)(t2, ..., tN)?或者?t1?是指針時(shí)?((*t1).*f)(t2, ..., tN).
2.?若?N == 1?且?f?是類?T?的數(shù)據(jù)成員指針:?INVOKE(f, t1)?等價(jià)于?t1.*f, 或者指針形式?(*t1).*f.
3.?均不滿足上面的情況表明?f?是一個(gè)函數(shù)對(duì)象(Function Object)?:?INVOKE(f, t1, t2, ..., tN)?等價(jià)于?f(t1, t2, ..., tN).
同時(shí), 對(duì)于成員函數(shù)指針和數(shù)據(jù)成員指針,?t1?可以是一個(gè)常規(guī)指針或一個(gè)重載了?operator*?的類的對(duì)象, 例如智能指針?std::unique_ptr?或?std::shared_ptr.
可作為參數(shù)的標(biāo)準(zhǔn)庫(kù)
下列標(biāo)準(zhǔn)庫(kù)設(shè)施接受任何可調(diào)用(Callable)類型:
庫(kù) | 說(shuō)明 |
function(C++11) | 包裝具有指定函數(shù)調(diào)用簽名的任意_可復(fù)制構(gòu)造類型_的可調(diào)用對(duì)象 (類模板) |
bind(C++11) | 綁定一或多個(gè)實(shí)參到函數(shù)對(duì)象 (函數(shù)模板) |
reference_wrapper(C++11) | 可復(fù)制構(gòu)造 (CopyConstructible)且可復(fù)制賦值 (CopyAssignable)的引用包裝器 (類模板) |
result_of (C++11)(C++20 中移除) invoke_result(C++17) | 推導(dǎo)以一組實(shí)參調(diào)用一個(gè)可調(diào)用對(duì)象的結(jié)果類型 (類模板) |
thread (構(gòu)造函數(shù)) | 構(gòu)造新的 thread 對(duì)象 (std::thread?的公開成員函數(shù)) |
call_once(C++11) | 僅調(diào)用函數(shù)一次, 即使從多個(gè)線程調(diào)用 (函數(shù)模板) |
async(C++11) | 異步運(yùn)行一個(gè)函數(shù)(有可能在新線程中執(zhí)行),并返回保有其結(jié)果的?std::future(函數(shù)模板) |
packaged_task(C++11) | 打包一個(gè)函數(shù), 存儲(chǔ)其返回值以進(jìn)行異步獲取 (類模板) |
一些典型的 Callable 類型
函數(shù)對(duì)象 Function Object
一個(gè)重載了括號(hào)操作符()的對(duì)象, 也就是可以以f(args)形式進(jìn)行函數(shù)調(diào)用的對(duì)象.
?
#include#include using?namespace?std; class?Add?{ public: ????const?int?operator()(const?int?a,const?int?b){ ????????return?a+b;} }; int?main()?{ ????Add?addFunction;?//函數(shù)對(duì)象 ????cout< ?
我的第一印象是它跟函數(shù)指針有什么區(qū)別? 就像是個(gè)函數(shù)執(zhí)行包裝器, 一個(gè)對(duì)象型的函數(shù)指針?
但是函數(shù)對(duì)象本質(zhì)上還是一個(gè) class 的具體化 object, 里面是可以附帶一些成員變量(可以理解為函數(shù)對(duì)象的狀態(tài)(state))的, 這就讓函數(shù)對(duì)象的應(yīng)用場(chǎng)景比函數(shù)指針更廣闊. 最典型的便是 STL 里了. C++ 的 STL 中的眾多 algorithm, 非常依賴于函數(shù)對(duì)象處理容器的元素. 想按照 STL 算法里的要求實(shí)現(xiàn)其功能要提供一些函數(shù)對(duì)象作為參數(shù), 即謂詞參數(shù)(predicate). 例如對(duì)于?find_if?算法.
?
class?NoLess{ public: ????NoLess(int?min?=?0):m_min(min){} ????bool?operator()?(int?value)?const{ ????????return?value?>=?m_min;} private: ????int?m_min; }; find_if(dest.begin(),dest.end(),NoLess(10));??//dest容器里找是否存在不小于10的元素?
對(duì)于普通函數(shù)來(lái)說(shuō), 只要簽名一致, 其類型就是相同的, 是類型不安全的. 但是這并不適用于函數(shù)對(duì)象, 因?yàn)楹瘮?shù)對(duì)象的類型是其類的類型. 這樣, 函數(shù)對(duì)象有自己的類型, 這也意味著函數(shù)對(duì)象可以用于模板參數(shù), 這對(duì)泛型編程有很大提升. 因?yàn)楹瘮?shù)對(duì)象一般用于模板參數(shù), 模板一般會(huì)在編譯時(shí)會(huì)做一些優(yōu)化. 因此函數(shù)對(duì)象一般快于普通函數(shù). 類也可以在使用的時(shí)候動(dòng)態(tài)再產(chǎn)生, 節(jié)省成本.
既然是類, 那就有它的限制, 例如要注意, 如同其他所有對(duì)象(狹義上的對(duì)象, 我感覺內(nèi)置類型其實(shí)也可以被叫對(duì)象, 按場(chǎng)景區(qū)分吧)一樣, 如果 pass-by-value 的化, 對(duì)象里的成員變量是被復(fù)制進(jìn)去的, 一旦對(duì)象被析構(gòu)了, 里面的成員變量也是無(wú)法保存下來(lái)的. 所以可以 pass-by-reference/pointer.
函數(shù)指針并不是沒(méi)有其用處了, 對(duì)于 C API 庫(kù)里的某些函數(shù)不支持函數(shù)對(duì)象還是有用武之地的. 例如?
?里面的排序函數(shù)?qsort?只能調(diào)用函數(shù)指針. ?
void?qsort(?void?*ptr,?size_t?count,?size_t?size,int?(*comp)(const?void?*,?const?void?*)?);?
函數(shù)
除了普通的函數(shù), 當(dāng)然也包括類成員函數(shù).
這里不提及模板函數(shù), 因?yàn)槟0搴瘮?shù)的概念只存在于編譯期, 運(yùn)行期的函數(shù)沒(méi)有模板的概念, 都是經(jīng)過(guò)完全特化過(guò)的, 因此與普通函數(shù)/類成員函數(shù)的概念是一致的.函數(shù)指針
?
#include#include using?namespace?std; int?AddFunc(int?a,?int?b)??{?? ????return?a?+?b;}?? int?main()?{ ????int?(*Add1)?(int?a,?int?b);?//函數(shù)指針,函數(shù)名兩側(cè)的()不可省略 ????int?(*Add2)?(int?a,?int?b); ????Add1?=?&AddFunc; ????Add2?=?AddFunc;??????? ????cout?<(*Add1)?(3,?2)< ?
Lambda 匿名函數(shù)(調(diào)用對(duì)象)
好處是就地定義使用, 簡(jiǎn)潔, 易維護(hù).
基本形式
完整聲明:
?
[capture?list]?(params?list)?mutable?exception->?return?type?{?function?body?}?
各項(xiàng)具體含義如下
1.?capture list: 捕獲外部變量列表.
2.?params list: 形參列表.
3.?mutable指示符: 用來(lái)說(shuō)用是否可以修改捕獲的變量, 因?yàn)閘ambda的() operator() 默認(rèn)是 const 的.
4.?exception: 異常設(shè)定.
5.?return type: 返回類型, 允許省略 lambda 表達(dá)式的返回值定義.
6.?function body: 函數(shù)體.
捕獲形式:
捕獲形式 說(shuō)明 [] 不捕獲任何外部變量 [變量名, …] 默認(rèn)以值得形式捕獲指定的多個(gè)外部變量(用逗號(hào)分隔), 如果引用捕獲, 需要顯示聲明(使用?&?說(shuō)明符) [this] 以值的形式捕獲?this?指針 [=] 以值的形式捕獲所有外部變量 [&] 以引用形式捕獲所有外部變量 [=, &x] 變量x以引用形式捕獲,其余變量以傳值形式捕獲 [&, x] 變量x以值的形式捕獲,其余變量以引用形式捕獲 省略其中的某些成分來(lái)聲明”不完整”的Lambda表達(dá)式:
序號(hào) 格式 1 [capture list] (params list) -> return type {function body} 2 [capture list] (params list) {function body} 3 [capture list] {function body} 一些關(guān)于 lambda 表達(dá)式的細(xì)節(jié)
1.?延遲調(diào)用
按值捕獲與按引用捕獲的區(qū)別.?
int?a?=?0; auto?f?=?[=]{?return?a;?};??????//?按值捕獲外部變量 a?+=?1;?????????????????????????//?a被修改了 std::cout?<?
2.?lambda 表達(dá)式轉(zhuǎn)換成函數(shù)指針沒(méi)有捕獲變量的 lambda 表達(dá)式可以直接轉(zhuǎn)換為函數(shù)指針, 而捕獲變量的 lambda 表達(dá)式則不能轉(zhuǎn)換為函數(shù)指針.
?
typedef?void(*Ptr)(int*); Ptr?p?=?[](int*?p){delete?p;};??//?正確,?沒(méi)有狀態(tài)的?lambda?(沒(méi)有捕獲)的lambda表達(dá)式可以直接轉(zhuǎn)換為函數(shù)指針 Ptr?p1?=?[&](int*?p){delete?p;};??//?錯(cuò)誤,?有狀態(tài)的?lambda?不能直接轉(zhuǎn)換為函數(shù)指針?
3.?嵌套
?
int?m?=?[](int?x)?{?return?[](int?y)?{?return?y?*?2;?}(x)+6;?}(5);?//16?
4.?作為 STL 算法函數(shù)謂詞參數(shù):
?
std::vector?myvec{?3,?2,?5,?7,?3,?2?}; std::sort(myvec.begin(),?myvec.end(),?[](int?a,?int?b)?->?bool?{?return?a? ?
C++14 中的 lambda 新特性
1.?lambda 捕捉表達(dá)式/右值
?
//?利用表達(dá)式捕獲,可以更靈活地處理作用域內(nèi)的變量 int?x?=?4; auto?y?=?[&r?=?x,?x?=?x?+?1]?{?r?+=?2;?return?x?*?x;?}(); //?此時(shí)?x?更新為6,y?為25 //?直接用字面值初始化變量 auto?z?=?[str?=?"string"]{?return?str;?}(); //?此時(shí)z是const?char*?類型,存儲(chǔ)字符串?string //不能復(fù)制只能移動(dòng)的對(duì)象,可以用std::move初始化變量 auto?myPi?=?std::make_unique(3.1415); auto?circle_area?=?[pi?=?std::move(myPi)](double?r)?{?return?*pi?*?r?*?r;?}; cout?< ?
2.?泛型 lambda 表達(dá)式:
?
auto?add?=?[](auto?x,?auto?y)?{?return?x?+?y;?};//推斷類型 int?x?=?add(2,?3);???//?5 double?y?=?add(2.5,?3.5);??//?6.0?
函數(shù)適配器
將函數(shù)對(duì)象與其它函數(shù)對(duì)象, 或者特定的值, 或者特定的函數(shù)相互組合的產(chǎn)物. 由于組合特性, 函數(shù)適配器可以滿足特定的需求, 頭文件?
?定義了幾種函數(shù)適配器: std::bind(op, args...): 將函數(shù)對(duì)象?op?的參數(shù)綁定到特定的值?args.
std::mem_fn(op): 將類的成員函數(shù)轉(zhuǎn)化為一個(gè)函數(shù)對(duì)象.
std::not1(op), std::not2(op),std::unary_negate,std::binary_negate: 一元取反器和二元取反器.std::bind
這里的函數(shù)對(duì)象就包括了上面所有的類型, 當(dāng)然也包含自己, 因此可以利用?std::bind?封裝出很多有意思的功能.
下面的例子來(lái)自于分享.??嵌套
?
//?定義一個(gè)接收一個(gè)參數(shù),然后將參數(shù)加10再乘以2的函數(shù)對(duì)象 auto?plus10times2?=?std::bind(std::multiplies{}, ????????std::bind(std::plus {},?std::_1,?10),?2); cout?<{}, ????????std::bind(std::multiplies {},?std::_1,?std::_1), ????????std::_1); cout?< ?
??調(diào)用類中的成員函數(shù)
?
class?Person{ public: ????Person(const?string&?n)?:?name{?n?}?{} ????void?print()?const?{?cout?<?p{?Person{"Tick"},?Person{"Trick"}?}; ????//?調(diào)用成員函數(shù)print ????std::for_each(p.begin(),?p.end(),?std::bind(&Person::print,?std::_1)); ????//?此處的std::_1表示要調(diào)用的Person對(duì)象,所以相當(dāng)于調(diào)用arg1.print() ????//?輸出:?Tick???Trick ????std::for_each(p.begin(),?p.end(),?std::bind(&Person::print2,?std::_1, ????????"Person:?")); ????//?此處的std::_1表示要調(diào)用的Person對(duì)象,所以相當(dāng)于調(diào)用arg1.print2("Person:?") ????//?輸出:?Person:?Tick???Person:?Trick ????return?0; }?
??調(diào)用 lambda 表達(dá)式
?
vector?data{?1,?2,?3,?4?}; auto?func?=?std::bind([](const?vector &?data)?{?cout?< ?
??調(diào)用范圍內(nèi)函數(shù)
?
char?myToupper(char?c){ ????if?(c?>=?'a'?&&?c?<=?'z') ????????return?static_cast(c?-?'a'?+?'A'); ????return?c; } int?main() { ????string?s{?"Internationalization"?}; ????string?sub{?"Nation"?}; ????auto?pos?=?std::search(s.begin(),?s.end(),?sub.begin(),?sub.end(), ????????????????????????std::bind(std::equal_to {},? ????????????????????????????std::bind(myToupper,?std::_1), ????????????????????????????std::bind(myToupper,?std::_2))); ????if?(pos?!=?s.end()){ ????????cout?< ?
??默認(rèn) pass-by-value, 如果想要 pass-by-reference, 需要用?std::ref?和?std::cref?包裝.
std::cref?比?std::ref?增加?const?屬性.?
void?f(int&?n1,?int&?n2,?const?int&?n3){ ????cout?<"In?function:?"?<?
std::mem_fn
與?std::bind?相比,?std::mem_fn?的范圍又要小一些, 僅調(diào)用成員函數(shù), 并且可以省略掉用于調(diào)用對(duì)象的占位符.
因此使用?std::men_fn?不需要綁定參數(shù), 可以更方便地調(diào)用成員函數(shù).?
vector?p{?Person{?"Tick"?},?Person{?"Trick"?}?}; std::for_each(p.begin(),?p.end(),?std::mem_fn(&Person::print)); //?輸出:?Trick?Trick Person?n{?"Bob"?}; std::mem_fn(&Person::print2)(n,?"Person:?"); //?輸出:?Person:?Bob ?
std::mem_fn?還可以調(diào)用成員變量
?
class?Foo{ public: ????int?data?=?7; ????void?display_greeting()?{?cout?<"Hello,?world. ";?} ????void?display_number(int?i)?{?cout?<"number:?"?<?
std::not1 、std::not2、std::unary_negate、std::binary_negate
std::not1,?std::not2?分別構(gòu)造一個(gè)與謂詞結(jié)果相反的一元/二元函數(shù)對(duì)象.
std::unary_negate,?std::binary_negate?分別返回其所保有的一元/二元謂詞的邏輯補(bǔ)的包裝函數(shù)對(duì)象, 其對(duì)象一般為?std::not1,?std::not2?構(gòu)造的函數(shù)對(duì)象,即又加了一層包裝.
下面分別是其使用示例:?
//std::not1 #include?#include? #include? int?main(int?argc,?char?**argv)? {?? ????std::vector ?nums?=?{5,?3,?4,?9,?1,?7,?6,?2,?8}; ????std::function ?less_than_5?=?[](int?x){?return?x?<=?5;?}; ????//?count?numbers?of?integer?that?not?less?and?equal?than?5 ????std::cout?<?nums?=?{5,?3,?4,?9,?1,?7,?6,?2,?8}; ????std::function ?ascendingOrder?=?[](int?a,?int?b)?{?return?a #include? #include? #include? struct?less_than_7?:?std::unary_function { ????bool?operator()(int?i)?const?{?return?i?7;?} }; ? int?main() { ????std::vector ?v; ????for?(int?i?=?0;?i?10;?++i)?v.push_back(i); ????std::unary_negate ?not_less_than_7((less_than_7())); ????std::cout?<{ ????bool?operator()(int?a,?int?b)?const?{?return?a?==?b;?} }; ? int?main() { ????std::vector ?v1; ????std::vector ?v2; ????for?(int?i?=?0;?i?10;?++i)?v1.push_back(i); ????for?(int?i?=?0;?i?10;?++i)?v2.push_back(10?-?i); ????std::vector ?v3(v1.size()); ????std::binary_negate ?not_same((same())); ????std::transform(v1.begin(),?v1.end(),?v2.begin(),?v3.begin(),?not_same); ? ????std::cout.setf(std::boolalpha); ????for?(int?i?=?0;?i?10;?++i) ????????std::cout?< ?
std::not_fn
注意 C++17 已經(jīng)把上面的?std::not1,?std::not2,?std::unary_negate?和?std::binary_negate?拋棄, 統(tǒng)一由?std::not_fn?替代.
?
//移除把滿足謂詞p的元素都copy到容器中 template?auto?FilterRemoveCopyIf(const?std::vector &?vec,?Pred?p)?{ ????std::vector ?out; ????std::remove_copy_if(begin(vec),?end(vec),? ????????????????????????std::back_inserter(out),?std::not_fn(p)); ????return?out; } ?
std::function
五花八門的?Callable, 個(gè)個(gè)都是人才, 但是不好帶(不好實(shí)現(xiàn) generic programming), 所以一個(gè)把所有 callable 對(duì)象封裝成統(tǒng)一形式的類型模板.
std::function?的實(shí)例可以對(duì)任何可以調(diào)用的目標(biāo)實(shí)體進(jìn)行存儲(chǔ), 復(fù)制, 和調(diào)用操作, 實(shí)現(xiàn)一種類型安全的包裹.基礎(chǔ)介紹
原型為:
?
template?//R是返回值類型,Args是函數(shù)的參數(shù)類型 class?function; ?
其存儲(chǔ)的可調(diào)用對(duì)象被稱為?std::function?的目標(biāo). 若?std::function?不含目標(biāo), 則稱它為空. 調(diào)用空?std::function?的目標(biāo)導(dǎo)致拋出?std::bad_function_call?異常.
std::function?滿足可復(fù)制構(gòu)造 (Copy Constructible) 和可復(fù)制賦值 (Copy Assignable) (參考).
瑞士軍刀一般的功能, 代碼例子如下:?
#include?#include? ? struct?Foo?{ ????Foo(int?num)?:?num_(num)?{} ????void?print_add(int?i)?const?{?std::cout?<?f_display?=?print_num; ????f_display(-9); ? ????//?存儲(chǔ)?lambda ????std::function ?f_display_42?=?[]()?{?print_num(42);?}; ????f_display_42(); ? ????//?存儲(chǔ)到?std::bind?調(diào)用的結(jié)果 ????std::function ?f_display_31337?=?std::bind(print_num,?31337); ????f_display_31337(); ? ????//?存儲(chǔ)到成員函數(shù)的調(diào)用 ????std::function ?f_add_display?=?&Foo::print_add; ????const?Foo?foo(314159); ????f_add_display(foo,?1); ????f_add_display(314159,?1); ? ????//?存儲(chǔ)到數(shù)據(jù)成員訪問(wèn)器的調(diào)用 ????std::function ?f_num?=?&Foo::num_; ????std::cout?<"num_:?"?<?f_add_display2?=?std::bind(?&Foo::print_add,?foo,?_1?); ????f_add_display2(2); ? ????//?存儲(chǔ)到成員函數(shù)和對(duì)象指針的調(diào)用 ????std::function ?f_add_display3?=?std::bind(?&Foo::print_add,?&foo,?_1?); ????f_add_display3(3); ? ????//?存儲(chǔ)到函數(shù)對(duì)象的調(diào)用 ????std::function ?f_display_obj?=?PrintNum(); ????f_display_obj(18); ? ????auto?factorial?=?[](int?n)?{ ????????//?存儲(chǔ)?lambda?對(duì)象以模擬"遞歸?lambda?",注意額外開銷 ????????std::function ?fac?=?[&](int?n){?return?(n?2)???1?:?n*fac(n-1);?}; ????????//?note?that?"auto?fac?=?[&](int?n){...};"?does?not?work?in?recursive?calls ????????return?fac(n); ????}; ????for?(int?i{5};?i?!=?8;?++i)?{?std::cout?< ?
可能的輸出
?
-9 42 31337 314160 314160 num_:?314159 314161 314162 18 5!?=?120;??6!?=?720;??7!?=?5040;?
回調(diào)函數(shù)
std::function?的應(yīng)用之一: 結(jié)合?typedef?定義函數(shù)類型構(gòu)造回調(diào)函數(shù).
?
typedef?std::function?CallBack; Class?MessageProcessor?{ private: ????CallBack?callback_; public: ????MessageProcessor(Callback?callback):callback_(callback){} ????void?ProcessMessage(const?std::string&?msg)?{ ????????callback_(msg); ????} }; ?
審核編輯:湯梓紅
?
評(píng)論
查看更多