網(wǎng)站技術(shù)的解決方案鄭州網(wǎng)絡(luò)營(yíng)銷(xiāo)哪個(gè)好
目錄
一、C++11的簡(jiǎn)介
二、萬(wàn)能引用與完美轉(zhuǎn)發(fā)
1、萬(wàn)能引用:模板中的 && 引用
2、完美轉(zhuǎn)發(fā):保持萬(wàn)能引用左右值屬性的解決方案
三、可變參數(shù)模板?
1、可變參數(shù)模板的基本使用
2、push 系列和 emplace 系列的區(qū)別
四、lambda表達(dá)式(重點(diǎn))
1、函數(shù)對(duì)象(又名仿函數(shù))
2、lambda 表達(dá)式
2.1? lambda 表達(dá)式的書(shū)寫(xiě)格式
1、捕捉列表
2、參數(shù)列表
3、mutable
4、->返回值類(lèi)型
5、函數(shù)體
6、小總結(jié)
3、各種類(lèi)型的 lambda 表達(dá)式代碼示例
4、lambda表達(dá)式的一些注意事項(xiàng)?
5、lambda表達(dá)式的底層原理
五、常用包裝器:function?
1、代碼引入
2、function 包裝器的作用
3、function 包裝器的使用
3.1? 模板原型
3.2? 使用舉例
六、通用函數(shù)適配器:bind?
1、模板原型
?2、使用舉例(重點(diǎn))
七、C++11標(biāo)準(zhǔn)線(xiàn)程庫(kù)
0、C++標(biāo)準(zhǔn)線(xiàn)程庫(kù)常用函數(shù)表格
1、標(biāo)準(zhǔn)線(xiàn)程庫(kù)使用的一些注意事項(xiàng)
?2、線(xiàn)程函數(shù)的參數(shù)
3、線(xiàn)程函數(shù)的返回值
4、原子性操作庫(kù)(atomic)
4.1? C++98 線(xiàn)程安全解決方案:對(duì)欲修改的共享數(shù)據(jù)加鎖
4.1.1? 互斥鎖的類(lèi)型
4.1.2? 加鎖解鎖的函數(shù)
4.1.3? 死鎖問(wèn)題
4.1.4? lock_guard VS unique_lock(小重點(diǎn))
4.2? C++11 提供的另一種線(xiàn)程安全解決方案:原子操作
本章將介紹繼C++98以來(lái)更新最大的一個(gè)標(biāo)準(zhǔn),也是實(shí)際開(kāi)發(fā)中用的最多的重要標(biāo)準(zhǔn),C++11標(biāo)準(zhǔn)。
由于C++11增加了非常多的語(yǔ)法特性,筆者學(xué)識(shí)有限,也很難一一介紹,在此主要講解一些比較實(shí)用的語(yǔ)法。
一、C++11的簡(jiǎn)介
公元2003年,C++標(biāo)準(zhǔn)委員會(huì)曾經(jīng)提交了一份技術(shù)勘誤表(簡(jiǎn)稱(chēng)TC1),使得 C++03 這個(gè)名字取代了 C++98 稱(chēng)為 C++11 之前的最新C++標(biāo)準(zhǔn)名稱(chēng)。=
不過(guò)由于 C++03(TC1) 主要是對(duì) C++98 標(biāo)準(zhǔn)中的漏洞進(jìn)行修復(fù),并沒(méi)有改動(dòng)語(yǔ)言的核心部分,因此人們習(xí)慣性的把兩個(gè)標(biāo)準(zhǔn)合并稱(chēng)為?C++98/03標(biāo)準(zhǔn)。
從 C++0x 到 C++11,C++標(biāo)準(zhǔn)十年磨一劍,第二個(gè)真正意義上的標(biāo)準(zhǔn)C++11才終于珊珊來(lái)遲。
相較于?C++98/03,C++11帶來(lái)了數(shù)量可觀(guān)的變化——其中包含了約140個(gè)新特性,以及對(duì)C++03標(biāo)準(zhǔn)中約600個(gè)缺陷的修正;這使得C++11更像是從C++98/03中孕育出的一種新語(yǔ)言。
相比較而言, C++11能更好地用于系統(tǒng)開(kāi)發(fā)和庫(kù)開(kāi)發(fā)、語(yǔ)法更加泛華和簡(jiǎn)單化、更加穩(wěn)定和安全,不僅功能更強(qiáng)大,而且能提升程序員的開(kāi)發(fā)效率,公司實(shí)際項(xiàng)目開(kāi)發(fā)中也用得比較多,值得重點(diǎn)去學(xué)習(xí)。
我們?cè)谇拔闹幸呀?jīng)提到了兩個(gè)C++11標(biāo)準(zhǔn)中的重要內(nèi)容:
《C/C++(四)類(lèi)和對(duì)象》第五小節(jié)第3小點(diǎn):右值引用和移動(dòng)語(yǔ)義
《C/C++(七)RAII思想與智能指針》全文
因此關(guān)于這兩篇文章所提到的有關(guān)C++11的知識(shí)點(diǎn),本章在此不做贅述,諸位看官若感興趣,可直接點(diǎn)擊跳轉(zhuǎn)至相應(yīng)文章觀(guān)看。
cppreference(C++學(xué)習(xí)手冊(cè))中有關(guān)C++11標(biāo)準(zhǔn)的詳細(xì)介紹
二、萬(wàn)能引用與完美轉(zhuǎn)發(fā)
1、萬(wàn)能引用:模板中的 && 引用
我們?cè)?strong> C/C++(四)中介紹了右值引用,即 &&引用,作用是給右值取別名;
但是在模板中,&& 就不是代表右值引用了,編譯器會(huì)把其處理為萬(wàn)能引用——即既能接收左值引用,又能接收右值引用。(又名引用折疊,即傳右值為右值引用,傳左值 && 會(huì)被折疊成 & 即左值引用)
我們給出一段代碼:
#include <iostream> using namespace std;void Fun(int& x) { cout << "左值引用" << endl; } void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; } void Fun(const int&& x) { cout << "const 右值引用" << endl; }template<typename T> void PerfectForward(T&& t) {Fun(t); }int main() {PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8; PerfectForward(b); // const 左值 PerfectForward(std::move(b)); // const 右值return 0; }
![]()
觀(guān)察運(yùn)行結(jié)果,發(fā)現(xiàn)全部變成了左值引用 運(yùn)行結(jié)果為什么會(huì)是這樣呢??
這是因?yàn)?#xff0c;模板的萬(wàn)能引用只是提供了可以同時(shí)接受左值引用和右值引用的能力,但是當(dāng)把 t?直接傳遞給
Fun
函數(shù)時(shí),t
已經(jīng)是一個(gè)具有名字的變量,因此它總是被視為一個(gè)左值。如果我們想在傳遞參數(shù)過(guò)程中始終保持其左右值屬性怎么辦呢?這時(shí)候就需要完美轉(zhuǎn)發(fā)了。
2、完美轉(zhuǎn)發(fā):保持萬(wàn)能引用左右值屬性的解決方案
完美轉(zhuǎn)發(fā)的關(guān)鍵字是 std::forward<模板類(lèi)型名>( 參數(shù)名 ),作用是可以保持傳參時(shí)參數(shù)的屬性,傳左值就是左值,傳右值就是右值。
我們修改一下上面的代碼:
#include <iostream> using namespace std;void Fun(int& x) { cout << "左值引用" << endl; } void Fun(const int& x) { cout << "const 左值引用" << endl; }void Fun(int&& x) { cout << "右值引用" << endl; } void Fun(const int&& x) { cout << "const 右值引用" << endl; }template<typename T> void PerfectForward(T&& t) {Fun(forward<T>(t)); }int main() {PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8; PerfectForward(b); // const 左值 PerfectForward(std::move(b)); // const 右值return 0; }
![]()
可以發(fā)現(xiàn)運(yùn)行結(jié)果正確了
三、可變參數(shù)模板?
1、可變參數(shù)模板的基本使用
在C++98/03中,類(lèi)模板和函數(shù)模板中,只能包含固定數(shù)量的模板參數(shù);
而C++11引入了新特性可變參數(shù)模板,讓我們可以接受可變參數(shù)的函數(shù)模板與類(lèi)模板。
由于可變參數(shù)模板使用比較抽象晦澀,在此點(diǎn)到為止,只介紹一些基礎(chǔ)的可變參數(shù)模板特性便足夠使用了。
下面這個(gè)就是一個(gè)基本可變參數(shù)的函數(shù)模板:
// Args是一個(gè)模板可變參數(shù)包,表示模板可以接受任意數(shù)量和類(lèi)型的參數(shù) template <class ...Args>// args是一個(gè)函數(shù)可變參數(shù)包,表示函數(shù)可以接受任意數(shù)量和類(lèi)型的參數(shù)。 void Test(Args... args) {}
可以看到,模板可變參數(shù)是在模板類(lèi)型名前面加上 ... ,函數(shù)可變參數(shù)就是在類(lèi)型名后面加上 ... 。
我們把帶省略號(hào)的參數(shù)稱(chēng)作“參數(shù)包”,里面可以包含任意個(gè)參數(shù)。
2、push 系列和 emplace 系列的區(qū)別
C++使用手冊(cè)中vector容器的emplace_back函數(shù)
C++使用手冊(cè)中 list 容器的emplace_back函數(shù)
template <class... Args> void emplace_back (Args&&... args);
我們可以看到,emplace 系列的接口,相較于push 系列,都是支持萬(wàn)能引用與可變參數(shù)模板的。
#include <iostream> #include <list> using namespace std;int main() {// 下面我們?cè)囈幌聨в锌截悩?gòu)造和移動(dòng)構(gòu)造的 string 類(lèi)型// 我們會(huì)發(fā)現(xiàn)差別:emplace_back是直接構(gòu)造了,push_back是先構(gòu)造,再移動(dòng)構(gòu)造std::list< std::pair<int, string> > mylist;// 多參數(shù)時(shí),可以分開(kāi)一個(gè)個(gè)傳參(因?yàn)?emplace_back 的形參是可變參數(shù)包,直接把參數(shù)包不斷往下傳,直接構(gòu)造到節(jié)點(diǎn)上)mylist.emplace_back(10, "sort");mylist.emplace_back(make_pair(20, "sort"));// 隱式類(lèi)型轉(zhuǎn)換,先接受參數(shù)構(gòu)造,再移動(dòng)構(gòu)造mylist.push_back(make_pair(30, "sort"));mylist.push_back({ 40, "sort" });for (auto e : mylist){cout << e.first << ":" << e.second << endl;}return 0; }
所以:
push
?系列接口:適用于已經(jīng)存在的對(duì)象,需要先構(gòu)造對(duì)象再?gòu)?fù)制或移動(dòng)到容器中。emplace
?系列接口:適用于直接在容器中構(gòu)造對(duì)象,避免了不必要的臨時(shí)對(duì)象創(chuàng)建和銷(xiāo)毀,提高了性能。(淺拷貝時(shí)以及參數(shù)較多時(shí)相較于 push 系列性能提升巨大,深拷貝時(shí)也會(huì)高效一些,但提升幅度沒(méi)有這么大)
四、lambda表達(dá)式(重點(diǎn))
1、函數(shù)對(duì)象(又名仿函數(shù))
函數(shù)對(duì)象,又名仿函數(shù),即可以像函數(shù)一樣使用的對(duì)象,實(shí)際上就是在類(lèi)里面重載了 operator() 運(yùn)算符的類(lèi)對(duì)象。
在C++98中,如果想要為某些自定義元素排序,就需要利用仿函數(shù)來(lái)定義排序時(shí)的比較規(guī)則
#include <iostream> #include <algorithm> #include <vector> using namespace std;struct Goods {Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}string _name; // 名字double _price; // 價(jià)格int _evaluate; // 評(píng)價(jià) }; struct ComparePriceLess {// 價(jià)格倒序排序bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;} }; struct ComparePriceGreater {bool operator()(const Goods& gl, const Goods& gr){// 價(jià)格正序排序return gl._price > gr._price;} };int main() {vector<Goods> v = { { "蘋(píng)果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠蘿", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());}
我們可以發(fā)現(xiàn),為了實(shí)現(xiàn)自定義類(lèi)型元素的排序,針對(duì)每一種排序規(guī)則都要去實(shí)現(xiàn)一個(gè)類(lèi),類(lèi)里還要重載operator(),顯得有些不便。
因此,C++11中出現(xiàn)了lambda表達(dá)式用來(lái)輕量級(jí)代替函數(shù)對(duì)象
2、lambda 表達(dá)式
2.1? lambda 表達(dá)式的書(shū)寫(xiě)格式
[ 捕捉列表 ](參數(shù)列表)mutable ->返回值類(lèi)型{函數(shù)體}
1、捕捉列表
捕捉列表總是出現(xiàn)在 lambda 表達(dá)式的開(kāi)頭,編譯器根據(jù)捕捉列表來(lái)判斷接下來(lái)的代碼是否為 lambda 函數(shù)。(因此必須要寫(xiě),不能省略)
作用:可以根據(jù)列表捕捉項(xiàng)來(lái)捕捉 lambda 表達(dá)式所在的函數(shù)的作用域中的可用變量,使其直接成為 lambda 函數(shù)的成員變量。
列表捕捉項(xiàng):描述了上下文中哪些數(shù)據(jù)可以被lambda函數(shù)所使用,以及傳參的方式是傳值還是傳引用。
有哪些列表捕捉項(xiàng)呢:
[變量名]:表示傳值捕捉某變量,并傳值傳參,傳值捕捉來(lái)的變量不可被修改。
[&變量名]:以引用的方式捕捉某變量,并傳引用傳參。(注意不要與取地址混淆了,只有在捕捉列表里是引用傳遞捕捉變量)
[=]:表示傳值捕捉并傳參包含 lambda表達(dá)式的代碼塊中的所有變量。(包括 this)
[&]:表示傳引用捕捉并傳參包含 lambda表達(dá)式的代碼塊中的所有變量。(包括 this)
[this]:表示值傳遞方式捕捉當(dāng)前 this 指針。
2、參數(shù)列表
lambda 表達(dá)式的參數(shù)列表與普通函數(shù)的參數(shù)列表一致,如果不需要傳參,可以連同括號(hào)一起省略。
3、mutable
在默認(rèn)情況下, lambda函數(shù)中按值捕獲的變量不可被修改。
添加 mutable 關(guān)鍵字,可以取消 lambda 函數(shù)中按值捕獲的變量的常量性,使之可以被修改不想取消可以省略(注意:如果使用了該修飾符,參數(shù)列表將不能省略)
4、->返回值類(lèi)型
采用追蹤返回的形式聲明函數(shù)返回值類(lèi)型。如果沒(méi)有返回值 / 返回值類(lèi)型可以由編譯器明確推導(dǎo)出來(lái),可以省略。
5、函數(shù)體
與普通函數(shù)的函數(shù)體一致,必須要寫(xiě)。(該函數(shù)體內(nèi)除了可以使用參數(shù)列表中的參數(shù),還可以使用由捕捉列表捕捉到的變量)
6、小總結(jié)
在 lambda 表達(dá)式的定義里,(參數(shù)列表)、mutable、->返回值類(lèi)型都是可以省略的部分,而捕捉列表與函數(shù)體不能省略。(因此,最簡(jiǎn)單的 lambda 表達(dá)式就是:[]{},但是沒(méi)有任何意義)
3、各種類(lèi)型的 lambda 表達(dá)式代碼示例
#include <iostream> using namespace std;int main() {// 1、最簡(jiǎn)單的lambda表達(dá)式[] {};// 2、省略參數(shù)列表,mutable和返回值類(lèi)型,返回值類(lèi)型由編譯器推導(dǎo)出來(lái)int a = 3, b = 4;[=] {return a + b; };// 3、省略返回值類(lèi)型(無(wú)返回值類(lèi)型)和mutableauto fun1 = [&](int c) {return a + c; }; // 注意,lambda表達(dá)式必須要有變量來(lái)接收,才能使用fun1(10); // 使用也是類(lèi)似于函數(shù)cout << a << " " << b << endl;// 4、各方面都比較完善的lambda表達(dá)式auto fun2 = [=, &b](int c)->int {return b += (a + c); };fun2(1);cout << b << endl;// 5、傳值捕捉x,添加mutable使之能被修改int x = 10;auto fun3 = [x](int a)mutable->int { a += 2; x *= 2; return x + a; }; }
值得注意的是,lambda表達(dá)式實(shí)際上可以被理解為無(wú)名函數(shù),不能直接調(diào)用,想要直接調(diào)用,必須通過(guò) auto 賦值給一個(gè)變量。(auto 也是C++11更新的關(guān)鍵字,用于自動(dòng)推導(dǎo)類(lèi)型)
4、lambda表達(dá)式的一些注意事項(xiàng)?
1、捕捉列表可由多個(gè)捕捉項(xiàng)組成,并以逗號(hào)分割。
(eg:[=, &a, &b]:以引用傳遞的方式捕捉變量a和b,值傳遞方式捕捉其他所有變量
[&,a, this]:值傳遞方式捕捉變量a和this,引用方式捕捉其他變量)
2、捕捉列表不允許變量重復(fù)傳遞,否則就會(huì)導(dǎo)致編譯錯(cuò)誤。
(eg:[=, a]:=已經(jīng)以值傳遞方式捕捉了所有變量,捕捉a重復(fù))
3、在代碼塊作用域以外的lambda函數(shù)捕捉列表必須為空。
4、在塊作用域中的lambda函數(shù)僅能捕捉父作用域中局部變量,捕捉任何非此作用域或者
非局部變量都會(huì)導(dǎo)致編譯報(bào)錯(cuò)。
5、lambda表達(dá)式之間不能相互賦值,即使看起來(lái)類(lèi)型相同
5、lambda表達(dá)式的底層原理
#include <iostream> using namespace std;class Rate { public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){return money * _rate * year;} private:double _rate; };int main() {// 函數(shù)對(duì)象double rate = 0.49;Rate r1(rate);r1(10000, 2);// lambda表達(dá)式auto r2 = [=](double monty, int year)->double {return monty * rate * year; };r2(10000, 2);return 0; }
之前說(shuō)過(guò),從使用方式來(lái)看,lambda表達(dá)式的使用方式與函數(shù)對(duì)象完全一樣。
在這個(gè)代碼里,函數(shù)對(duì)象將rate作為其成員變量,在定義對(duì)象時(shí)給出初始值即可,lambda表達(dá)式通過(guò)捕獲列表則可以直接將該變量捕獲到。
其實(shí),底層編譯器對(duì) lambda 表達(dá)式的處理方式就是完全比照函數(shù)對(duì)象來(lái)的,底層每一個(gè)lambda 表達(dá)式都會(huì)生成一個(gè)仿函數(shù)類(lèi)。
![]()
可以發(fā)現(xiàn),C++底層,lambda表達(dá)式與仿函數(shù)的處理完全一致
五、常用包裝器:function?
fuction 包裝器又稱(chēng)適配器;C++中的function本質(zhì)是一個(gè)類(lèi)模板,是為了解決模板低效問(wèn)題而出現(xiàn)的,可以統(tǒng)一類(lèi)型
1、代碼引入
#include <iostream> using namespace std;template <class F, class T>T useF(F f, T x) {static int count = 0;cout << "count: " << count++ << endl;cout << "count: " << &count << endl;return f(x); }double f(double i) {return i / 2; }struct Functor {double operator()(double d){return d / 3;} };int main() {// useF的模板參數(shù)做函數(shù)名cout << useF(f, 10.27) << endl;// useF的模板參數(shù)做仿函數(shù)cout << useF(Functor(), 10.27) << endl;// useF的模板參數(shù)做lambda表達(dá)式cout << useF([](double d)->double {return d / 4; }, 10.27) << endl;return 0; }
觀(guān)察這段程序的運(yùn)行結(jié)果我們可以發(fā)現(xiàn),一套模板可以調(diào)用的類(lèi)型非常豐富:函數(shù)指針、仿函數(shù)、lambda表達(dá)式。
但是他們的運(yùn)行卻是相互獨(dú)立的,每一種類(lèi)型都會(huì)實(shí)例化出一份模板,這會(huì)造成模板效率下降。
應(yīng)該如何解決呢?使用 function 包裝器統(tǒng)一類(lèi)型。
2、function 包裝器的作用
函數(shù)指針、仿函數(shù)、lambda表達(dá)式這三大可調(diào)用對(duì)象各有一定的缺陷:
函數(shù)指針用起來(lái)比較復(fù)雜苦澀而且局限性很大;仿函數(shù)比較臃腫笨重而且要全局定義;lambda 表達(dá)式又無(wú)法判斷其類(lèi)型只能依靠 auto 讓編譯器自己推導(dǎo)。
因此 function 包裝器就出現(xiàn)了,其作用就是把可調(diào)用對(duì)象給包裝起來(lái),對(duì)類(lèi)型進(jìn)行統(tǒng)一,同時(shí)解決模板低效問(wèn)題。(PS:類(lèi)成員函數(shù)也屬于可調(diào)用對(duì)象,也可以用function 將其包裝起來(lái))(但是 function 本身并不是可調(diào)用對(duì)象,需要包裝可調(diào)用對(duì)象才能使用)
3、function 包裝器的使用
前言:使用std::function 需要包含頭文件 <functional>
3.1? 模板原型
template <class T> function;template <class Ret, class ...Args> class function<Ret(Args...)>;/*模板參數(shù)說(shuō)明:Ret:被調(diào)用對(duì)象的返回類(lèi)型Args:被調(diào)用對(duì)象的形參,是個(gè)可變參數(shù)列表,可以包裝各式參數(shù) */
3.2? 使用舉例
#include <iostream> #include <functional> using namespace std;template <class F, class T>// 函數(shù)模板 T useF(F f, T x) {static int count = 0;cout << "count: " << ++count << endl;cout << "count: " << &count << endl;return f(x); }// 函數(shù) double f(double i) {return i / 2; }// 仿函數(shù) struct Functor {double operator()(double d){return d / 3;} };class Test { public:double minus(double x, double y){return x - y;}double add(double x, double y){return x + y;} }; int main() {// 1、function包裝函數(shù)名(函數(shù)指針)function<double(double)> func1 = f;cout << useF(func1,10.27) << endl;// 2、function包裝仿函數(shù)function<double(double)> func2 = Functor();cout << useF(func2, 10.27) << endl;// 3、function包裝lambda表達(dá)式function<double(double)> func3 = [](double x)->double {return x; };cout << useF(func3, 10.27) << endl;// 4、function包裝類(lèi)成員函數(shù)// 注意:類(lèi)成員函數(shù)的包裝比較特殊,可變參數(shù)包需要多一個(gè)this指針,底層通過(guò)這個(gè)this指針調(diào)用函數(shù);包裝的可調(diào)用函數(shù)也需要加類(lèi)域 和 &function<double(Test, double, double)> func4 = &Test::add;cout << func4(Test(), 20.15, 10.27) << endl;return 0; }
觀(guān)察運(yùn)行結(jié)果可以發(fā)現(xiàn),函數(shù)模板只實(shí)例化了一份,實(shí)現(xiàn)了類(lèi)型統(tǒng)一。
六、通用函數(shù)適配器:bind?
std::bind 同樣定義在 <functional> 頭文件中,就像一個(gè)函數(shù)適配器,接收可調(diào)用對(duì)象(一般與 function 搭配),生成新的可調(diào)用對(duì)象來(lái)“適應(yīng)”源對(duì)象參數(shù)列表,多用來(lái)調(diào)節(jié)可調(diào)用對(duì)象參數(shù)的個(gè)數(shù)和順序,讓可調(diào)用對(duì)象的參數(shù)使用更加靈活。
一般而言,我們可以用 bind 把一個(gè)原本接收n個(gè)參數(shù)的函數(shù),通過(guò)綁定一些參數(shù),返回一個(gè)可以只接收其中部分參數(shù)的新函數(shù)。
1、模板原型
// 模板1:不指定返回類(lèi)型,通用性較強(qiáng),編譯器通過(guò)fn的類(lèi)型自動(dòng)推斷
template <class Fn, class ...Args>
bind(Fn&& fn, Args&&... args);// 模板2:指定返回類(lèi)型Ret,使用的時(shí)候需要bind<Ret>顯式指明返回類(lèi)型,防止返回類(lèi)型出錯(cuò)
template <class Ret, class Fn, class ...Args>
bind(Fn&& fn, Args&&... args);/*參數(shù)類(lèi)型說(shuō)明:fn:萬(wàn)能引用,傳可調(diào)用對(duì)象args:可變參數(shù)包
*/
其中模板1是最常用的,因此我們調(diào)用 bind 的一般形式是:
auto NewCallable = bind(callable, arg_list);
其中,NewCallable 本身也需要是一個(gè)可調(diào)用對(duì)象;arg_list 是一個(gè)逗號(hào)分隔的參數(shù)列表,對(duì)應(yīng)callable 中的參數(shù)。(注意:arg_liist 里面大概率會(huì)出現(xiàn) _n (n為正整數(shù))的占位符,用來(lái)表示參數(shù)在 NewCallable 中的位置,_1為newCallable的第一個(gè)參數(shù),_2為第二個(gè)參數(shù),以此類(lèi)推)
當(dāng)我們調(diào)用NewCallable時(shí),NewCallable 會(huì)調(diào)用 callable,并把a(bǔ)rg_list 中的參數(shù)傳給它。
?2、使用舉例(重點(diǎn))
#include <iostream>
#include <functional>
using namespace std;int add(int x, int y)
{return x + y;
}class Test
{
public:int minus(int x, int y){return x - y;}
};int main()
{// func1 綁定函數(shù) add,且參數(shù)由調(diào)用 func1, func2 的第一、二個(gè)參數(shù)指定function<int(int, int)> func1 = bind(add, placeholders::_1, placeholders:: _2);cout << func1(2014, 2015) << endl;// func2 也綁定函數(shù)add,但是參數(shù)明確給出來(lái)(func2 的類(lèi)型由于兩個(gè)參數(shù)全部寫(xiě)死,類(lèi)型實(shí)際上變成了function<int()>)function<int()> func2 = bind(add, 2014, 2015);cout << func2() << endl;// func3 綁定成員函數(shù)(綁定成員函數(shù),需要有對(duì)應(yīng)實(shí)例來(lái)調(diào)用,同時(shí)需要取地址,確認(rèn)是哪個(gè)類(lèi)的哪個(gè)成員函數(shù),不取編譯器無(wú)法識(shí)別)Test t;function<int(int, int)> func3 = bind(&Test::minus, t, placeholders::_1, placeholders::_2);cout << func3(2015, 2014) << endl;// func4 改變參數(shù)的位置(即func4的第一個(gè)參數(shù)由傳遞的第二個(gè)參數(shù)決定,反之亦然)function<int(int, int)> func4 = bind(&Test::minus, t, placeholders::_2, placeholders::_1);cout << func4(2015, 2014) << endl;// func5 也可以調(diào)整傳參的個(gè)數(shù)// 可以把每次都要固定傳的參數(shù)給綁死,在調(diào)用的時(shí)候簡(jiǎn)化調(diào)用。由于寫(xiě)死了一個(gè)形參,所以function的類(lèi)型也跟著改變?yōu)閒unction<int(int)>function<int(int)> func5 = bind(&Test::minus, t, 2013, placeholders::_1);cout << func5(2012) << endl;return 0;
}
七、C++11標(biāo)準(zhǔn)線(xiàn)程庫(kù)
在C++11之前,涉及到多線(xiàn)程問(wèn)題,都是和平臺(tái)相關(guān)的,比如在Windows和Linux下各有自己的多線(xiàn)程接口,這使得代碼的可移植性較差。?
因此C++11中最重要的特性就是提供了對(duì)線(xiàn)程的支持,使得C++在并行編程時(shí)不需要再依賴(lài)第三方庫(kù)了。(同時(shí)在原子操作中還引入了原子類(lèi)的概念)
使用C++11標(biāo)準(zhǔn)線(xiàn)程庫(kù),需要包含頭文件 <thread>
C++使用手冊(cè)中關(guān)于 thread 線(xiàn)程類(lèi)的詳細(xì)介紹
thread() | 無(wú)參構(gòu)造函數(shù),構(gòu)造一個(gè)沒(méi)有關(guān)聯(lián)任何線(xiàn)程函數(shù)的線(xiàn)程對(duì)象,不會(huì)啟動(dòng)任何線(xiàn)程 |
thread(fn,args1,args2……) | 構(gòu)造出一個(gè)線(xiàn)程對(duì)象,該對(duì)象關(guān)聯(lián)了可執(zhí)行對(duì)象 fn,args1、args2、……為線(xiàn)程函數(shù)的參數(shù) |
get_id() | 獲取 thread 類(lèi)型的線(xiàn)程ID |
?joinable() | 判斷線(xiàn)程是否還在執(zhí)行,如果線(xiàn)程對(duì)象關(guān)聯(lián)了一個(gè)正在執(zhí)行的線(xiàn)程,返回 true; 如果線(xiàn)程對(duì)象沒(méi)有關(guān)聯(lián)任何線(xiàn)程,或者線(xiàn)程已經(jīng)結(jié)束已經(jīng)被回收,返回 false |
?join() | 會(huì)使當(dāng)前線(xiàn)程阻塞,直到被調(diào)用的線(xiàn)程完全執(zhí)行完畢,子線(xiàn)程結(jié)束后還會(huì)自動(dòng)回收子線(xiàn)程的資源;用來(lái)保證線(xiàn)程同步 |
detach() | 在創(chuàng)建線(xiàn)程對(duì)象后馬上調(diào)用,用于把被創(chuàng)建線(xiàn)程與線(xiàn)程對(duì)象分離開(kāi),分離 的線(xiàn)程變?yōu)楹笈_(tái)線(xiàn)程,創(chuàng)建的線(xiàn)程的"死活"就與主線(xiàn)程無(wú)關(guān) |
1、標(biāo)準(zhǔn)線(xiàn)程庫(kù)使用的一些注意事項(xiàng)
1、線(xiàn)程是操作系統(tǒng)中的一個(gè)概念,線(xiàn)程對(duì)象可以關(guān)聯(lián)一個(gè)線(xiàn)程,用來(lái)控制線(xiàn)程和獲取線(xiàn)程狀態(tài)。
(也因?yàn)榫€(xiàn)程對(duì)象的存在,可以把線(xiàn)程以對(duì)象的形式往容器里面放)
2、創(chuàng)建一個(gè)線(xiàn)程對(duì)象后,如果沒(méi)有提供線(xiàn)程函數(shù),該對(duì)象實(shí)際沒(méi)有對(duì)應(yīng)任何線(xiàn)程。
3、如果創(chuàng)建一個(gè)線(xiàn)程對(duì)象,并且給該線(xiàn)程對(duì)象關(guān)聯(lián)線(xiàn)程函數(shù),程序運(yùn)行后,該線(xiàn)程就會(huì)被啟動(dòng),與主線(xiàn)程一起并發(fā)運(yùn)行。(線(xiàn)程函數(shù)一般關(guān)聯(lián)可調(diào)用對(duì)象:函數(shù)指針、lambda表達(dá)式、函數(shù)對(duì)象)
?4、thread線(xiàn)程類(lèi)是防拷貝的,不允許拷貝構(gòu)造以及賦值,但是支持移動(dòng)構(gòu)造和移動(dòng)賦值。(即將一個(gè)線(xiàn)程對(duì)象關(guān)聯(lián)的線(xiàn)程轉(zhuǎn)移給其他線(xiàn)程對(duì)象,轉(zhuǎn)移期間不影響線(xiàn)程的執(zhí)行)
5、可以通過(guò) jionable()函數(shù)來(lái)判斷線(xiàn)程是否有效,如果是以下任意情況,則線(xiàn)程無(wú)效:
?采用無(wú)參構(gòu)造函數(shù)構(gòu)造的線(xiàn)程對(duì)象
?線(xiàn)程對(duì)象的狀態(tài)已經(jīng)轉(zhuǎn)移給其他線(xiàn)程對(duì)象
?線(xiàn)程已經(jīng)調(diào)用jion或者detach結(jié)束
?2、線(xiàn)程函數(shù)的參數(shù)
線(xiàn)程函數(shù)在默認(rèn)傳參的時(shí)候,是以傳值方式傳參的,想要保留其引用特性,需要使用 std::ref(參數(shù)名)函數(shù)。(當(dāng)然傳指針沒(méi)有這種問(wèn)題)
(只要線(xiàn)程函數(shù)參數(shù)是左值引用,傳參的時(shí)候,必須使用 ref() 函數(shù),把參數(shù)包裝成引用,才能保證左值引用正確傳參——底層是先把參數(shù)傳給 thread 線(xiàn)程類(lèi)的構(gòu)造函數(shù),再傳給線(xiàn)程函數(shù),由于線(xiàn)程庫(kù)底層某些復(fù)雜的原因,所有的參數(shù)傳過(guò)去都會(huì)變成右值。)
#include <iostream>
#include <thread>
using namespace std;void ThreadFunc1(int& x)
{x += 10;
}
void ThreadFunc2(int* x)
{(*x) += 10;
}void ThreadFunc3(const string& s)
{cout << s << endl;
}int main()
{int x = 2015;// 通過(guò) ref 函數(shù)保持其引用屬性,否則會(huì)因?yàn)樽詈蠼Y(jié)果是右值導(dǎo)致報(bào)錯(cuò)沒(méi)有對(duì)應(yīng)函數(shù)thread t1(ThreadFunc1, ref(x));t1.join();cout << x << endl;// 傳指針,不存在此類(lèi)問(wèn)題thread t2(ThreadFunc2, &x);t2.join();cout << x << endl;// 傳右值就可以,不會(huì)報(bào)錯(cuò)string s = "二零一五";thread t3(ThreadFunc3, s);t3.join();return 0;
}
3、線(xiàn)程函數(shù)的返回值
我們一般很難拿到線(xiàn)程函數(shù)的返回值,如果想得到某種結(jié)果,可以在線(xiàn)程函數(shù)中多加一個(gè)輸出型參數(shù)用來(lái)承載結(jié)果。
4、原子性操作庫(kù)(atomic)
多線(xiàn)程最主要的問(wèn)題是共享數(shù)據(jù)帶來(lái)的問(wèn)題(即線(xiàn)程安全問(wèn)題)。如果共享數(shù)據(jù)都是只讀的,那么沒(méi)問(wèn) 題,因?yàn)橹蛔x操作不會(huì)影響到數(shù)據(jù),更不會(huì)涉及對(duì)數(shù)據(jù)的修改,所以所有線(xiàn)程都會(huì)獲得同樣的數(shù) 據(jù)。但是,當(dāng)一個(gè)或多個(gè)線(xiàn)程要修改共享數(shù)據(jù)時(shí),就會(huì)產(chǎn)生很多潛在的問(wèn)題。
那么應(yīng)該如何解決線(xiàn)程安全問(wèn)題呢?
4.1? C++98 線(xiàn)程安全解決方案:對(duì)欲修改的共享數(shù)據(jù)加鎖
4.1.1? 互斥鎖的類(lèi)型
C++ 中有許多互斥鎖類(lèi),他們有各自的作用
C++中互斥鎖類(lèi)的類(lèi)型 mutex 最常用的普通互斥鎖 recursive_mutex 遞歸互斥鎖,在遞歸的時(shí)候使用,因?yàn)橐话愕幕コ怄i在遞歸時(shí)會(huì)死鎖(底層簡(jiǎn)單來(lái)說(shuō)就是在鎖資源被占用后,不會(huì)第一時(shí)間阻塞,而是先判斷是不是當(dāng)前線(xiàn)程,如果不是,就不阻塞,可以繼續(xù)執(zhí)行) timed_mutex 時(shí)間互斥鎖,可以設(shè)置超時(shí)時(shí)間(相對(duì)/絕對(duì),一般用相對(duì)),到了之后鎖才可以加/解鎖 recursive_timed_mutex 遞歸時(shí)間互斥鎖 4.1.2? 加鎖解鎖的函數(shù)
C++加鎖解鎖的函數(shù)也有許多
lock() 最基礎(chǔ)且常用的阻塞式加鎖,如果鎖資源被占用,線(xiàn)程在此阻塞 try_lock() 防阻塞式鎖,先試一下能不能加鎖,判斷一下鎖資源有沒(méi)有被占用,如果沒(méi)有就上鎖,不能字節(jié)返回,不會(huì)向 lock()一樣阻塞線(xiàn)程
try_lock_for(std::chrono::duration<Rep, Period> rel_time)
timed_mutex 時(shí)間互斥鎖專(zhuān)屬阻塞式鎖;在設(shè)置的相對(duì)超時(shí)時(shí)間(只要寫(xiě)一下秒數(shù)即可,因此時(shí)間互斥鎖加鎖常用這個(gè)而不是絕對(duì)超超時(shí)時(shí)間)里嘗試獲取鎖,如果鎖已被占用,則阻塞當(dāng)前線(xiàn)程,直到超時(shí)時(shí)間到達(dá)或鎖可用 try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time)
:timed_mutex 時(shí)間互斥鎖專(zhuān)屬阻塞式鎖;在設(shè)置的絕對(duì)超時(shí)時(shí)間(即具體時(shí)間)里嘗試獲取鎖,如果鎖已被占用,則阻塞當(dāng)前線(xiàn)程,直到超時(shí)時(shí)間到達(dá)或鎖可用 unlock() 解鎖 4.1.3? 死鎖問(wèn)題
C++中存在拋異常機(jī)制,一旦發(fā)生拋異常,會(huì)跳轉(zhuǎn)到捕獲異常處,后面的代碼不再執(zhí)行,這可能會(huì)導(dǎo)致鎖資源沒(méi)有被及時(shí)釋放,造成死鎖:
#include <iostream> #include <thread> #include <mutex> #include <cstdlib> // 包含 srand 和 rand #include <ctime> // 包含 timeusing namespace std;// 實(shí)現(xiàn)一個(gè)拋異常函數(shù) void ThrowException() {if (rand() % 6 == 0){throw "異常";} }int main() {mutex mtx;int num = 100; // 循環(huán)取隨機(jī)數(shù)的次數(shù)// 初始化隨機(jī)數(shù)生成器srand(static_cast<unsigned int>(time(nullptr)));thread t1([num, &mtx]() {try{for (int i = 0; i < num; i++){mtx.lock();ThrowException();mtx.unlock();}}catch (const char* s){printf("這是線(xiàn)程1的異常: %s\n", s);}});thread t2([num, &mtx]() {try{for (int i = 0; i < num; i++){mtx.lock();ThrowException();mtx.unlock();}}catch (const char* s){printf("這是線(xiàn)程2的異常: %s\n", s);}});// 等待線(xiàn)程結(jié)束t1.join();t2.join();return 0; }
![]()
可以看到,線(xiàn)程1先搶占了資源,然后由于拋異常導(dǎo)致鎖資源沒(méi)有釋放,導(dǎo)致線(xiàn)程2一直阻塞,造成死鎖 那么應(yīng)該如何解決呢?這就要用到我們 C/C++(七)中所提到的重要思想:RAII思想了,把資源交給一個(gè)類(lèi)對(duì)象管理,借助其析構(gòu)函數(shù)進(jìn)行解鎖釋放空間等操作。
#include <iostream> #include <thread> #include <mutex> #include <cstdlib> // 包含 srand 和 rand #include <ctime> // 包含 timeusing namespace std;// 實(shí)現(xiàn)一個(gè)拋異常函數(shù) void ThrowException() {if (rand() % 6 == 0){throw "異常";} }// 利用RAII思想實(shí)現(xiàn)一個(gè)類(lèi)用于托管資源 template <class Lock> class LockGuard { public:LockGuard(Lock& mtx):_mtx(mtx){_mtx.lock();}~LockGuard(){_mtx.unlock();} private:Lock& _mtx; //注意是引用類(lèi)型,并不創(chuàng)建新鎖 }; int main() {mutex mtx;int num = 100; // 循環(huán)取隨機(jī)數(shù)的次數(shù)// 初始化隨機(jī)數(shù)生成器srand(static_cast<unsigned int>(time(nullptr)));thread t1([num, &mtx]() {try{for (int i = 0; i < num; i++){LockGuard<mutex> lg(mtx);ThrowException();}}catch (const char* s){printf("這是線(xiàn)程1的異常: %s\n", s);}});thread t2([num, &mtx]() {try{for (int i = 0; i < num; i++){LockGuard<mutex> lg(mtx);ThrowException();}}catch (const char* s){printf("這是線(xiàn)程2的異常: %s\n", s);}});// 等待線(xiàn)程結(jié)束t1.join();t2.join();return 0; }
![]()
這時(shí)候就可以正常運(yùn)行了 PS:這里的RAII資源托管類(lèi)不需要手搓,C++11提供了兩個(gè)模板類(lèi):lock_guard 和 unique_lock
4.1.4? lock_guard VS unique_lock(小重點(diǎn))
lock_guard模板類(lèi):只支持構(gòu)造函數(shù)和析構(gòu)函數(shù),拷貝構(gòu)造被禁止,且使用方式很單一,只能在構(gòu)造函數(shù)和析構(gòu)函數(shù)的時(shí)候進(jìn)行加鎖和解鎖。
unique_lock模板類(lèi):同樣不支持拷貝構(gòu)造函數(shù),但是支持手動(dòng)加鎖和解鎖,使用方式更加靈活,提供了更多的成員函數(shù)。
某位前輩大佬整理的完整版 lock_guard 與 unique_lock 的區(qū)別
4.2? C++11 提供的另一種線(xiàn)程安全解決方案:原子操作
加鎖雖然可以解決線(xiàn)程安全問(wèn)題,但是很多時(shí)候用的都是阻塞鎖,會(huì)影響程序的運(yùn)行效率;而且還可能會(huì)出現(xiàn)死鎖問(wèn)題。
所以 C++11 就提供了原子操作(即不可被中斷的一個(gè)/一系列操作)和原子類(lèi)型,(必須包含頭文件 <atomic>)
(PS:不過(guò)原子操作一般都是用在臨界區(qū)較小,操作較短的內(nèi)置類(lèi)型操作當(dāng)中,不需要加鎖。線(xiàn)程可以直接對(duì)原子類(lèi)型變量進(jìn)行互斥地訪(fǎng)問(wèn);對(duì)于較長(zhǎng)的操作 / 自定義類(lèi)型的操作就不建議使用原子操作了,因?yàn)樵硬僮魍ǔ?huì)使用自旋鎖(spin lock),這意味著線(xiàn)程在請(qǐng)求資源失敗后,會(huì)不斷嘗試獲取資源,而不是阻塞等待。如果操作時(shí)間較長(zhǎng),這種自旋鎖會(huì)導(dǎo)致 CPU 資源浪費(fèi))
? ![]()
內(nèi)置類(lèi)型對(duì)應(yīng)原子類(lèi)型表,大部分原子類(lèi)型就是前面加個(gè)atomic_ #include <iostream> #include <thread> #include <atomic> using namespace std;void fun(atomic<long>& num, size_t count) {for (size_t i = 0; i < count; i++){num++; // 原子操作,不需要加鎖也能實(shí)現(xiàn)互斥訪(fǎng)問(wèn)} }int main() {atomic<long> num = 0;cout << "原子操作前,num = " << num << std::endl;thread t1(fun, ref(num), 1000000);thread t2(fun, ref(num), 1000000);t1.join();t2.join();cout << "原子操作后, num = " << num << std::endl;return 0; }