【C++】C++11 新特性
目錄
1.列表初始化
1.1. C++98中使用{}初始化的問題
1.2. 內(nèi)置類型的列表初始化
1.3. 自定義類型的列表初始化
2. 變量類型推導(dǎo)
2.1. 為什么需要類型推導(dǎo)
2.2. decltype類型推導(dǎo)
2.2.1 為什么需要decltype
2.2.2. decltype
3. 對(duì)默認(rèn)成員的控制(default、delete)
3.1. 顯式缺省函數(shù)
3.2. 刪除默認(rèn)函數(shù)
3.3. final和override
4. 右值引用
4.1. 概念
4.2. 右值與左值
4.3. 左值引用與右值引用
4.4. 左值引用的缺陷
4.5. 移動(dòng)語義
4.6. 移動(dòng)構(gòu)造和移動(dòng)賦值
4.7. 完美轉(zhuǎn)發(fā)
5. lambda表達(dá)式
5.1. lambda表達(dá)式語法
6. 包裝器
6.1. 為什么需要包裝器
6.2. 包裝器的使用
6.3. bind包裝器
6.3.1. bind包裝器改變參數(shù)位置
6.3.2. bind包裝器綁定固定參數(shù)
7. thread線程庫
7.1. thread線程庫函數(shù)介紹
7.2. 線程函數(shù)參數(shù)
7.3. lock_guard與unique_lock
7.3.1. Mutex的種類
7.3.2. lock_guard
7.3.3. unique_lock
7.4. 原子性操作庫(atomic)
8. 條件變量(condition_variable)
1.列表初始化
1.1. C++98中使用{}初始化的問題
在C++98中,標(biāo)準(zhǔn)允許使用花括號(hào){}對(duì)數(shù)組元素進(jìn)行統(tǒng)一的列表初始值設(shè)定。比如:
int array1[] = {1,2,3,4,5}; int array2[5] = {0};對(duì)于一些自定義的類型,卻無法使用這樣的初始化。比如:
vector<int> v{1,2,3,4,5};就無法通過編譯,導(dǎo)致每次定義vector時(shí),都需要先把vector定義出來,然后使用循環(huán)對(duì)其賦初始值,非常不方便。C++11為了兼容C語言的這種特性,擴(kuò)大了用大括號(hào)括起的列表(初始化列表)的使用范圍,使其可用于所有的內(nèi)置類型和用戶自定義的類型,使用初始化列表時(shí),可添加等號(hào)(=),也可不添加。
1.2. 內(nèi)置類型的列表初始化
int main() { // 內(nèi)置類型變量 int x1 = {10}; int x2{10}; int x3 = 1+2; int x4 = {1+2}; int x5{1+2}; // 數(shù)組 int arr1[5] {1,2,3,4,5}; int arr2[]{1,2,3,4,5}; // 動(dòng)態(tài)數(shù)組,在C++98中不支持 int* arr3 = new int[5]{1,2,3,4,5}; // 標(biāo)準(zhǔn)容器 vector<int> v{1,2,3,4,5}; map<int, int> m{{1,1}, {2,2,},{3,3},{4,4},make_pair(5,5)}; return 0; }注意:列表初始化可以在{}之前使用等號(hào),其效果與不使用=沒有什么區(qū)別.
1.3. 自定義類型的列表初始化
1.標(biāo)準(zhǔn)庫支持單個(gè)對(duì)象的列表初始化
class Point { public:Point(int x = 0, int y = 0): _x(x), _y(y){} private:int _x;int _y; }; int main() {Pointer p{ 1, 2 };return 0; }2.多個(gè)對(duì)象的列表初始化
多個(gè)對(duì)象想要支持列表初始化,需給該類(模板類)添加一個(gè)帶有initializer_list類型參數(shù)的構(gòu)造函數(shù)即可。注意:initializer_list是系統(tǒng)自定義的類模板,該類模板中主要有三個(gè)方法:begin()、end()迭代器以及獲取區(qū)間中元素個(gè)數(shù)的方法size()。
其底層可以看作是使用數(shù)組暫時(shí)將需要初始化的數(shù)據(jù)存儲(chǔ)起來。
例如:這里簡(jiǎn)單實(shí)現(xiàn)以下vector底層的初始化列表:
#include <initializer_list> template<class T> class Vector { public:Vector(initializer_list<T> l): _capacity(l.size()), _size(0) {_array = new T[_capacity];for(auto e : l)_array[_size++] = e; } Vector<T>& operator=(initializer_list<T> l) {_array = new T[_capacity];size_t i = 0;for (auto e : l)_array[i++] = e;return *this; }private:T* _array;size_t _capacity;size_t _size; };2. 變量類型推導(dǎo)
2.1. 為什么需要類型推導(dǎo)
在定義變量時(shí),必須先給出變量的實(shí)際類型,編譯器才允許定義,但有些情況下可能不知道需要實(shí)際類型怎么給,或者類型寫起來特別復(fù)雜,比如:
#include <map> #include <string> int main() {short a = 32670;short b = 32670;// c如果給成short,會(huì)造成數(shù)據(jù)丟失,如果能夠讓編譯器根據(jù)a+b的結(jié)果推導(dǎo)c的實(shí)際類型,就不會(huì)存在問題short c = a + b;std::map<std::string, std::string> m{{"apple", "蘋果"}, {"banana","香蕉"}};// 使用迭代器遍歷容器, 迭代器類型太繁瑣std::map<std::string, std::string>::iterator it = m.begin();while(it != m.end()){cout<<it->first<<" "<<it->second<<endl;++it;}return 0; }C++11中,可以使用auto來根據(jù)變量初始化表達(dá)式類型推導(dǎo)變量的實(shí)際類型,可以給程序的書寫提供許多方便。將程序中c與it的類型換成auto,程序可以通過編譯,而且更加簡(jiǎn)潔。
#include <map> #include <string> int main() {std::map<std::string, std::string> m{{"apple", "蘋果"}, {"banana","香蕉"}};auto it = m.begin();while(it != m.end()){cout<<it->first<<" "<<it->second<<endl;++it;}return 0; }2.2. decltype類型推導(dǎo)
2.2.1 為什么需要decltype
auto使用的前提是:必須要對(duì)auto聲明的類型進(jìn)行初始化,否則編譯器無法推導(dǎo)出auto的實(shí)際類型。但有時(shí)候可能需要根據(jù)表達(dá)式運(yùn)行完成之后結(jié)果的類型進(jìn)行推導(dǎo),因?yàn)榫幾g期間,代碼不會(huì)運(yùn)行,此時(shí)auto也就無能為力。
template<class T1, class T2> T1 Add(const T1& left, const T2& right) {return left + right; }template<class T1, class T2> auto Add(const T1& left, const T2& right) // 將返回值類型換成auto去自動(dòng)推導(dǎo),這里就會(huì)出錯(cuò) {return left + right; }如果能用加完之后結(jié)果的實(shí)際類型作為函數(shù)的返回值類型就不會(huì)出錯(cuò),但這需要程序運(yùn)行完才能知道結(jié)果的實(shí)際類型,即RTTI(Run-Time Type Identification 運(yùn)行時(shí)類型識(shí)別)。
C++98中確實(shí)已經(jīng)支持RTTI:typeid只能查看類型不能用其結(jié)果類定義類型dynamic_cast只能應(yīng)用于含有虛函數(shù)的繼承體系中運(yùn)行時(shí)類型識(shí)別的缺陷是降低程序運(yùn)行的效率。
2.2.2. decltype
decltype是根據(jù)表達(dá)式的實(shí)際類型推演出定義變量時(shí)所用的類型,比如:
1.推演表達(dá)式類型作為變量的定義類型
int main() {int a = 10;int b = 20;// 用decltype推演a+b的實(shí)際類型,作為定義c的類型decltype(a+b) c;cout<<typeid(c).name()<<endl; // typeid只能用作打印出對(duì)象的類型return 0; }2. 推演函數(shù)返回值的類型
void* func(size_t size) {return malloc(size); } int main() {// 如果沒有帶參數(shù),推導(dǎo)函數(shù)的類型cout << typeid(decltype(func)).name() << endl;// 如果帶參數(shù)列表,推導(dǎo)的是函數(shù)返回值的類型,注意:此處只是推演,不會(huì)執(zhí)行函數(shù)cout << typeid(decltype(func(0))).name() <<endl;return 0; }3. 對(duì)默認(rèn)成員的控制(default、delete)
在C++中對(duì)于 空類編譯器會(huì)生成一些默認(rèn)的成員函數(shù),比如: 構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、運(yùn)算符重載、析構(gòu)函數(shù)和&和const&的重載、移動(dòng)構(gòu)造、移動(dòng)拷貝構(gòu)造等函數(shù)。如果在類中顯式定義了,編譯器將不會(huì)重新生成默認(rèn)版本。 有時(shí)候這樣的規(guī)則可能被忘記,最常見的是聲明了帶參數(shù)的構(gòu)造函數(shù),必要時(shí)則需要定義不帶參數(shù)的版本以實(shí)例化無參的對(duì)象。而且 有時(shí)編譯器會(huì)生成,有時(shí)又不生成,容易造成混亂,于是C++11讓程序員可以控制是否需要編譯器生成。3.1. 顯式缺省函數(shù)
在C++11中,可以在默認(rèn)函數(shù)定義或者聲明時(shí)加上=default,從而顯式的指示編譯器生成該函數(shù)的默認(rèn)版本,用=default修飾的函數(shù)稱為顯式缺省函數(shù)。
比如看以下代碼:
#include<iostream> #include<string> using namespace std;class person { public://person(){}//person() = default;person(const person& p) //這里由于顯式的創(chuàng)建拷貝構(gòu)造,所以編譯器不會(huì)默認(rèn)生成構(gòu)造函數(shù),因?yàn)榭截悩?gòu)造也是特殊的構(gòu)造函數(shù){_age = p._age;_name = p._name;} private:int _age;string _name; };int main() {person p1; // 由于編譯器沒有生成默認(rèn)構(gòu)造函數(shù),所以這里在定義對(duì)象p1時(shí)會(huì)找不到默認(rèn)的構(gòu)造函數(shù)導(dǎo)致出錯(cuò)person p2 = p1; return 0; }所以這時(shí),如果不想顯式的寫出構(gòu)造函數(shù),就可以使用default,這樣編譯器就會(huì)認(rèn)為并沒有默認(rèn)構(gòu)造函數(shù),就會(huì)自動(dòng)生成。
person() = default;3.2. 刪除默認(rèn)函數(shù)
如果能想要限制某些默認(rèn)函數(shù)的生成,在C++98中,是該函數(shù)設(shè)置成private,并且不給定義,這樣只要其他人想要調(diào)用就會(huì)報(bào)錯(cuò)。在C++11中更簡(jiǎn)單,只需在該函數(shù)聲明加上=delete即可,該語法指示編譯器不生成對(duì)應(yīng)函數(shù)的默認(rèn)版本,稱=delete修飾的函數(shù)為刪除函數(shù)。
例如:不想讓一個(gè)類對(duì)象進(jìn)行拷貝構(gòu)造
class person { public:person(int age = 10, string name = "edward"):_age(age),_name(name){}person(const person& p) = delete; // C++11做法,使用delete關(guān)鍵字private:int _age;string _name;person(const person& p); // C++98做法:將拷貝構(gòu)造私有,并且只聲明不實(shí)現(xiàn) };int main() {person p1;return 0; }3.3. final和override
這兩個(gè)關(guān)鍵字也是C++11新增的,但是其實(shí)我們?cè)趯W(xué)習(xí)繼承和多態(tài)的時(shí)候已經(jīng)見過了,這里不再過多描述。
final:修飾類,使該類不能被繼承;修飾虛函數(shù),該虛函數(shù)不能被重寫。
override:檢查派生類虛函數(shù)是否重寫了基類某個(gè)虛函數(shù),如果沒有重寫編譯報(bào)錯(cuò)
4. 右值引用
4.1. 概念
C++98中提出了引用的概念,引用即別名,引用變量與其引用實(shí)體公共同一塊內(nèi)存空間,而引用的底層是通過指針來實(shí)現(xiàn)的,因此使用引用,可以提高程序的可讀性。
為了提高程序運(yùn)行效率,C++11中引入了右值引用,右值引用也是別名,但其只能對(duì)右值引用。
int fun(int n) {return n - 1; }int main() {int x = 1, y = 2;int&& a = 10; // 引用常量int&& b = x + y; // 引用表達(dá)式int&& c = fun(2); // 引用函數(shù)返回值return 0; }4.2. 右值與左值
左值與右值是C語言中的概念,但C標(biāo)準(zhǔn)并沒有給出嚴(yán)格的區(qū)分方式,一般認(rèn)為:可以放在=左邊的,或者能夠取地址的稱為左值,只能放在=右邊的,或者不能取地址的稱為右值,但是也不一定完全正確。
關(guān)于左值與右值的區(qū)分不是很好區(qū)分,一般認(rèn)為:
總結(jié):
C語言中的純右值,比如:a+b, 100
將亡值。比如:表達(dá)式的中間結(jié)果、函數(shù)按照值的方式進(jìn)行返回。
4.3. 左值引用與右值引用
那么這里有一個(gè)問題:左值引用能否引用右值,右值引用能否引用左值?
int main() {int x = 1, y = 2;int& ra = 10; // 左值引用引用右值int& rb = x + y;int&& rra = x; // 右值引用引用左指 int&& rrb = y;return 0; }這里通過編譯器可以看到是會(huì)報(bào)錯(cuò)的:
注意:
普通引用只能引用左值,不能引用右值,const引用既可引用左值,也可引用右值。C++11中右值引用:只能引用右值,一般情況不能直接引用左值,可以通過move將左值變成右值然后引用。
int main() {int x = 1, y = 2;const int& ra = x + y;int&& rra = std::move(x); // move:將x變成右值cout << ra << endl;cout << rra << endl;return 0; }4.4. 左值引用的缺陷
當(dāng)我們?cè)诤瘮?shù)中使用引用傳傳參時(shí)幾乎是沒有任何問題的,但是當(dāng)函數(shù)中的返回值使用引用返回時(shí)可能就會(huì)出現(xiàn)問題。
比如:我們使用string的實(shí)現(xiàn)作為例子
namespace wt {class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// s1.swap(s2)void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷貝構(gòu)造string(const string& s):_str(nullptr), _size(0), _capacity(0){cout << "string(const string& s) -- 深拷貝" << endl;string tmp(s._str);swap(tmp);}string& operator=(const string& s){cout << "string& operator=(string s) -- 深拷貝" << endl;string tmp(s);swap(tmp);return *this;}~string(){//cout << "~string()" << endl;delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}//string operator+=(char ch)string& operator+=(char ch){push_back(ch);return *this;}string operator+(char ch){string tmp(*this);push_back(ch);return tmp;}const char* c_str() const{return _str;}private:char* _str;size_t _size;size_t _capacity; // 不包含最后做標(biāo)識(shí)的\0};wt::string to_string(int value){wt::string str;while (value){int val = value % 10;str += ('0' + val);value /= 10;}reverse(str.begin(), str.end());return str;} }// 場(chǎng)景1 // 左值引用做參數(shù),基本完美的解決所有問題 void func1(wt::string s) {}void func2(const wt::string& s) {}// 場(chǎng)景2 // 左值引用做返回值,只能解決部分問題 // wt::string& operator+=(char ch) //解決了 // wt::string operator+(char ch) // 沒有解決,不能使用引用返回以前在學(xué)習(xí)拷貝構(gòu)造時(shí)我們學(xué)習(xí)過,當(dāng)上面的operator+這種情況,如果返回值是一個(gè)自定義類型,由于返回的是一個(gè)右值,所以在處理該函數(shù)的作用域之后,該右值會(huì)被立即銷毀。所以在返回之前,會(huì)調(diào)用一次拷貝構(gòu)造將返回值臨時(shí)保存在調(diào)用該函數(shù)的棧幀中,然后再將臨時(shí)值拷貝構(gòu)造給接收該函數(shù)的對(duì)象。(這里編譯器會(huì)優(yōu)化為一次拷貝構(gòu)造)
這里會(huì)發(fā)現(xiàn):返回值、拷貝構(gòu)造的臨時(shí)對(duì)象、ans每個(gè)對(duì)象創(chuàng)建后都有自己的獨(dú)立的空間,而且每個(gè)空間中的內(nèi)容也完全相同,相當(dāng)于創(chuàng)建了三個(gè)內(nèi)容完全相同的對(duì)象,對(duì)于空間是一種浪費(fèi),程序的效率也會(huì)降低,而且臨時(shí)對(duì)象確實(shí)作用不是很大 。
4.5. 移動(dòng)語義
C++11提出了移動(dòng)語義概念,即:將一個(gè)對(duì)象中資源移動(dòng)到另一個(gè)對(duì)象中的方式,可以有效緩解該問題。
// 移動(dòng)構(gòu)造string(string&& s) //創(chuàng)建一個(gè)空string對(duì)象:_str(nullptr), _size(0), _capacity(0){cout << "string(string&& s) -- 資源轉(zhuǎn)移" << endl;this->swap(s); // 將s內(nèi)部的資源轉(zhuǎn)移給空對(duì)象,由于s是右值后面會(huì)被釋放,所以這里不會(huì)對(duì)它造成影響}// 移動(dòng)賦值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 轉(zhuǎn)移資源" << endl;swap(s);return *this;} int main() {wt::string s1;wt::string s2 = wt::to_string(1234);cout << endl;s1 = wt::to_string(1234);return 0; }這里我們使用to_string(1234)函數(shù)的返回值去初始化s2和賦值給s1,如果沒有移動(dòng)構(gòu)造和移動(dòng)賦值,那么肯定是會(huì)去深拷貝的:
如果有移動(dòng)構(gòu)造和移動(dòng)賦值則不會(huì):
有了移動(dòng)語義,應(yīng)該慎用move,因?yàn)槿绻麑⒁粋€(gè)左值給move了,那么他內(nèi)部的資源就可能被轉(zhuǎn)移走了,這時(shí)再去使用這個(gè)左值對(duì)象就可能出現(xiàn)問題。
4.6. 移動(dòng)構(gòu)造和移動(dòng)賦值
原來C++類中,有6個(gè)默認(rèn)成員函數(shù):
最后重要的是前4個(gè),后兩個(gè)用處不大。默認(rèn)成員函數(shù)就是我們不寫編譯器會(huì)生成一個(gè)默認(rèn)的。
C++11新增了兩個(gè):移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符重載。
針對(duì)移動(dòng)構(gòu)造函數(shù)和移動(dòng)賦值運(yùn)算符重載有一些需要注意的點(diǎn)如下:
如果 自己沒有實(shí)現(xiàn)移動(dòng)構(gòu)造函數(shù),且沒有實(shí)現(xiàn)析構(gòu)函數(shù)、拷貝構(gòu)造、拷貝賦值重載中的任意一個(gè)。那么編譯器會(huì)自動(dòng)生成一個(gè)默認(rèn)移動(dòng)構(gòu)造。默認(rèn)生成的移動(dòng)構(gòu)造函數(shù),對(duì)于內(nèi)置類型成員會(huì)執(zhí)行逐成員按字節(jié)拷貝,自定義類型成員,則需要看這個(gè)成員是否實(shí)現(xiàn)移動(dòng)構(gòu)造,如果實(shí)現(xiàn)了就調(diào)用移動(dòng)構(gòu)造,沒有實(shí)現(xiàn)就調(diào)用拷貝構(gòu)造。如果 自己沒有實(shí)現(xiàn)移動(dòng)賦值重載函數(shù),且沒有實(shí)現(xiàn)析構(gòu)函數(shù)、拷貝構(gòu)造、考貝賦值重載中的任意一個(gè),那么編譯器會(huì)白動(dòng)生成一個(gè)默認(rèn)移動(dòng)賦值。默認(rèn)生成的移動(dòng)構(gòu)造函數(shù),對(duì)于內(nèi)置類型成員會(huì)執(zhí)行逐成員按字節(jié)拷貝,白定義類型成員,則需要看這個(gè)成員是否實(shí)現(xiàn)移動(dòng)賦值,如果實(shí)現(xiàn)了就調(diào)用移動(dòng)賦值,沒有實(shí)現(xiàn)就調(diào)用拷貝賦值。(默認(rèn)移動(dòng)賦值跟上面移動(dòng)構(gòu)造完全類似)
如果 你提供了移動(dòng)構(gòu)造或者移動(dòng)賦值,編譯器不會(huì)自動(dòng)提供拷貝構(gòu)造和拷貝賦值
4.7. 完美轉(zhuǎn)發(fā)
完美轉(zhuǎn)發(fā)是指在函數(shù)模板中,完全依照模板的參數(shù)的類型,將參數(shù)傳遞給函數(shù)模板中調(diào)用的另外一個(gè)函數(shù)。
假如有以下場(chǎng)景:
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; }根據(jù)上面的代碼產(chǎn)生的結(jié)果我們會(huì)發(fā)現(xiàn),為什么給函數(shù)中傳入的右值,再傳入Fun函數(shù)后全部匹配到了左值引用的函數(shù)?
模板中的&&不代表右值引用,而是萬能引用,其既能接收左值又能接收右值。模板的萬能引用只是提供了能夠接收同時(shí)接收左值引用和右值引用的能力,
但是引用類型的唯一作用就是限制了接收的類型,后續(xù)使用中都退化成了左值,
由于傳入的是右值,所以PerfectForward函數(shù)在接收他時(shí)會(huì)創(chuàng)建一塊臨時(shí)的空間保存它,這時(shí)這個(gè)右值就可以被取地址了,所以它的屬性就變成了右值!
我們希望能夠在傳遞過程中保持它的左值或者右值的屬性, 就需要用我們下面學(xué)習(xí)的完美轉(zhuǎn)發(fā):
Fun(std::forward<T>(t)); //forward<T> 完美轉(zhuǎn)發(fā):將參數(shù)按照傳遞給轉(zhuǎn)發(fā)函數(shù)的實(shí)際類型轉(zhuǎn)給目標(biāo)函數(shù),而不產(chǎn)生額外的開銷所謂完美:函數(shù)模板在向其他函數(shù)傳遞自身形參時(shí),如果相應(yīng)實(shí)參是左值,它就應(yīng)該被轉(zhuǎn)發(fā)為左值;如果相應(yīng)實(shí)參是右值,它就應(yīng)該被轉(zhuǎn)發(fā)為右值。
5. lambda表達(dá)式
從C語言的函數(shù)指針到C++98的仿函數(shù),在有些時(shí)候使用其實(shí)很不方便,特別是函數(shù)指針,所以C++11中添加了lambda表達(dá)式。
5.1. lambda表達(dá)式語法
lambda表達(dá)式書寫格式:[capture-list] (parameters) mutable -> return-type { statement }
lambda表達(dá)式各部分說明
[capture-list] : 捕捉列表,該列表總是出現(xiàn)在lambda函數(shù)的開始位置,編譯器根據(jù)[]來判斷接下來的代碼是否為lambda函數(shù),捕捉列表能夠捕捉上下文中的變量供lambda函數(shù)使用。
(parameters):參數(shù)列表。與普通函數(shù)的參數(shù)列表一致,如果不需要參數(shù)傳遞,則可以連同()一起省略
mutable:默認(rèn)情況下,lambda函數(shù)總是一個(gè)const函數(shù),mutable可以取消其常量性。使用該修飾符時(shí),參數(shù)列表不可省略(即使參數(shù)為空)。
->returntype:返回值類型。用追蹤返回類型形式聲明函數(shù)的返回值類型,沒有返回值時(shí)此部分可省略。返回值類型明確情況下,也可省略,由編譯器對(duì)返回類型進(jìn)行推導(dǎo)。
{statement}:函數(shù)體。在該函數(shù)體內(nèi),除了可以使用其參數(shù)外,還可以使用所有捕獲到的變量。
注意:
在lambda函數(shù)定義中,參數(shù)列表和返回值類型都是可選部分,而捕捉列表和函數(shù)體可以為空。
因此C++11中最簡(jiǎn)單的lambda函數(shù)為:[]{}; 該lambda函數(shù)不能做任何事情。
int main() {// 最簡(jiǎn)單的lambda表達(dá)式, 該lambda表達(dá)式?jīng)]有任何意義[]{};// 省略參數(shù)列表和返回值類型,返回值類型由編譯器推導(dǎo)為intint a = 3, b = 4;[=]{return a + 3; };// 省略了返回值類型,無返回值類型auto fun1 = [&](int c){b = a + c; };fun1(10)cout<< a <<" "<<b<<endl;// 各部分都很完善的lambda函數(shù)auto fun2 = [=, &b](int c)->int{return b += a+ c; };cout<<fun2(10)<<endl;// 賦值捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;return 0; }通過上述例子可以看出,lambda表達(dá)式實(shí)際上可以理解為無名函數(shù),該函數(shù)無法直接調(diào)用,如果想要直接調(diào)用,可借助auto將其賦值給一個(gè)變量。
捕獲列表說明
捕捉列表描述了上下文中那些數(shù)據(jù)可以被lambda使用,以及使用的方式傳值還是傳引用。
[var]:表示值傳遞方式捕捉變量var[=]:表示值傳遞方式捕獲所有父作用域中的變量(包括this)
[&var]:表示引用傳遞捕捉變量var
[&]:表示引用傳遞捕捉所有父作用域中的變量(包括this)
[this]:表示值傳遞方式捕捉當(dāng)前的this指針
注意:
a. 父作用域指包含lambda函數(shù)的語句塊
b. 語法上捕捉列表可由多個(gè)捕捉項(xiàng)組成,并以逗號(hào)分割。
比如:[=, &a, &b]:以引用傳遞的方式捕捉變量a和b,值傳遞方式捕捉其他所有變量 [&,a, this]:值傳遞方式捕捉變量a和this,引用方式捕捉其他變量
c. 捕捉列表不允許變量重復(fù)傳遞,否則就會(huì)導(dǎo)致編譯錯(cuò)誤。 比如:[=, a]:=已經(jīng)以值傳遞方式捕捉了所有變量,捕捉a重復(fù)
d. 在塊作用域以外的lambda函數(shù)捕捉列表必須為空。
e. 在塊作用域中的lambda函數(shù)僅能捕捉父作用域中局部變量,捕捉任何非此作用域或者非局部變量都會(huì)導(dǎo)致編譯報(bào)錯(cuò)。
f. lambda表達(dá)式之間不能相互賦值,即使看起來類型相同
實(shí)際在底層編譯器對(duì)于lambda表達(dá)式的處理方式,完全就是按照函數(shù)對(duì)象的方式處理的,即:如果定義了一個(gè)lambda表達(dá)式,編譯器會(huì)自動(dòng)生成一個(gè)類,在該類中重載了operator()。
6. 包裝器
6.1. 為什么需要包裝器
function包裝器 也叫作適配器。C++中的function本質(zhì)是一個(gè)類模板,也是一個(gè)包裝器。
- func可能是什么呢?那么func可能是函數(shù)名?函數(shù)指針?函數(shù)對(duì)象(仿函數(shù)對(duì)象)?也有可能
- 是lamber表達(dá)式對(duì)象?所以這些都是可調(diào)用的類型!
- 如此豐富的類型,可能會(huì)導(dǎo)致模板的效率低下! 為什么呢?
-
- 由于函數(shù)指針、仿函數(shù)、lambda表達(dá)式是不同的類型,因此useF函數(shù)會(huì)被實(shí)例化出三份,三次調(diào)用useF函數(shù)所打印count的地址也是不同的。
- 但實(shí)際這里根本沒有必要實(shí)例化出三份useF函數(shù),因?yàn)槿握{(diào)用useF函數(shù)時(shí)傳入的可調(diào)用對(duì)象雖然是不同類型的,但這三個(gè)可調(diào)用對(duì)象的返回值和形參類型都是相同的。
使用包裝器可以解決這里的問題:
template <class T> function; template <class Ret, class... Args> class function<Ret(Args...)>;模板參數(shù)說明
- Ret :被包裝的可調(diào)用對(duì)象的返回值類型。
- Args... :被包裝的可調(diào)用對(duì)象的形參類型。
6.2. 包裝器的使用
function包裝器可以對(duì)可調(diào)用對(duì)象進(jìn)行包裝,包括函數(shù)指針(函數(shù)名)、仿函數(shù)(函數(shù)對(duì)象)、lambda表達(dá)式、類的成員函數(shù)
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() {//函數(shù)名function<double(double)> func1 = f;cout << useF(func1, 22.22) << endl;//函數(shù)對(duì)象function<double(double)> func2 = Functor();cout << useF(func2, 33.33) << endl;//lambda表達(dá)式function<double(double)> func3 = [](double d)->double {return d / 4; };cout << useF(func3, 44.44) << endl;return 0; }用包裝器分別對(duì)著三個(gè)可調(diào)用對(duì)象進(jìn)行包裝,然后再用這三個(gè)包裝后的可調(diào)用對(duì)象來調(diào)用useF函數(shù),這時(shí)就只會(huì)實(shí)例化出一份useF函數(shù)。
根本原因就是因?yàn)榘b后,這三個(gè)可調(diào)用對(duì)象都是相同的function類型,因此最終只會(huì)實(shí)例化出一份useF函數(shù),該函數(shù)的第一個(gè)模板參數(shù)的類型就是function類型的。
當(dāng)包裝器包裝類的非靜態(tài)成員函數(shù)時(shí)需要額外注意:
class Plus { public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;} }; int main() {function<double(Plus, double, double)> func5 = &Plus::plusd; //&不可省略cout << func5(Plus(), 1.1, 2.2) << endl; // 需要傳入類對(duì)象去調(diào)用類中的非靜態(tài)成員函數(shù)return 0; }6.3. bind包裝器
bind也是一種函數(shù)包裝器,也叫做適配器。它可以接受一個(gè)可調(diào)用對(duì)象,生成一個(gè)新的可調(diào)用對(duì)象來“適應(yīng)”原對(duì)象的參數(shù)列表。
bind函數(shù)模板的原型
template <class Fn, class... Args> bind(Fn&& fn, Args&&... args);template <class Ret, class Fn, class... Args> bind(Fn&& fn, Args&&... args);- fn : 可調(diào)用對(duì)象。
- args... :要綁定的參數(shù)列表:值或占位符。
6.3.1. bind包裝器改變參數(shù)位置
int Plus(int a, int b) {return a - b; }int main() {function<int(int, int)> func = bind(Plus, placeholders::_2, placeholders::_1);// 將參數(shù)1與參數(shù)2交換位置cout << func(1, 2) << endl; //1return 0; }綁定時(shí)第一個(gè)參數(shù)傳入函數(shù)指針這個(gè)可調(diào)用對(duì)象,但后續(xù)傳入的要綁定的參數(shù)列表依次是placeholders::2和placeholders::1,表示后續(xù)調(diào)用新生成的可調(diào)用對(duì)象時(shí),傳入的第一個(gè)參數(shù)傳給placeholders::2,傳入的第二個(gè)參數(shù)傳給placeholders::1。
6.3.2. bind包裝器綁定固定參數(shù)
int Plus(int a, int b) {return a + b; }int main() {//綁定固定參數(shù)function<int(int)> func = bind(Plus, placeholders::_1, 10);cout << func(2) << endl; //12return 0; }- 想把Plus函數(shù)的第二個(gè)參數(shù)固定綁定為10,可以在綁定時(shí)將參數(shù)列表的placeholders::_2設(shè)置為10
- 此時(shí)調(diào)用綁定后新生成的可調(diào)用對(duì)象時(shí)就只需要傳入一個(gè)參數(shù),它會(huì)將該值與10相加后的結(jié)果進(jìn)行返回
bind包裝器的意義:
- 將一個(gè)函數(shù)的某些參數(shù)綁定為固定的值,讓我們?cè)谡{(diào)用時(shí)可以不用傳遞某些參數(shù)。
- 可以對(duì)函數(shù)參數(shù)的順序進(jìn)行靈活調(diào)整。
7. thread線程庫
7.1. thread線程庫函數(shù)介紹
在C++11之前,涉及到多線程問題,都是和平臺(tái)相關(guān)的,比如windows和linux下各有自己的接口,這使得代碼的可移植性比較差。
C++11中最重要的特性就是對(duì)線程進(jìn)行支持了,使得C++在并行編程時(shí)不需要依賴第三方庫,而且在原子操作中還引入了原子類的概念。
要使用標(biāo)準(zhǔn)庫中的線程,必須包含< thread >頭文件。
| 函數(shù)名 | 功能 |
| thread() | 構(gòu)造一個(gè)線程對(duì)象,沒有關(guān)聯(lián)任何線程函數(shù),即沒有啟動(dòng)任何線程 |
| thread(fn, args1, args2, ...) | 構(gòu)造一個(gè)線程對(duì)象,并關(guān)聯(lián)線程函數(shù)fn,args1,args2,...為線程函數(shù)的參數(shù) |
| get_id() | 獲取線程id |
| jionable() | 線程是否還在執(zhí)行,joinable代表的是一個(gè)正在執(zhí)行中的線程。 |
| jion() | 該函數(shù)調(diào)用后會(huì)阻塞住線程,當(dāng)該線程結(jié)束后,主線程繼續(xù)執(zhí)行 |
| detach() | 在創(chuàng)建線程對(duì)象后馬上調(diào)用,用于把被創(chuàng)建線程與線程對(duì)象分離開,分離的線程 變?yōu)楹笈_(tái)線程,創(chuàng)建的線程的"死活"就與主線程無關(guān) |
注意:
例如:
#include<iostream> #include<thread> using namespace std;void func(int n) {cout << this_thread::get_id() << endl; //打印該線程的idfor (int i = 0; i < n; ++i){cout << i << endl;} }int main() {thread t1(func, 10); //創(chuàng)建線程//thread(func, 10).detach(); //創(chuàng)建匿名線程 注意:匿名線程必須在創(chuàng)建時(shí)將線程分離,因?yàn)楹竺鏁?huì)找不到t1.join(); //線程等待// this_thread::sleep_for(std::chrono::seconds(3)); //使當(dāng)前線程休眠return 0; }其實(shí)這里線程的創(chuàng)建的方法與前面linux中學(xué)習(xí)的類似,只是C++11中用對(duì)象封裝了,使用起來更加方便了。
get_id()的返回值類型為id類型,id類型實(shí)際為std::thread命名空間下封裝的一個(gè)類(因?yàn)閃indows和Linux下對(duì)線程id處理的方式不同,C++中為了方便跨平臺(tái)的使用所以這樣處理),該類中包含了一個(gè)結(jié)構(gòu)體:
// vs下查看 typedef struct { /* thread identifier for Win32 */void *_Hnd; /* Win32 HANDLE */unsigned int _Id; } _Thrd_imp_t;當(dāng)創(chuàng)建一個(gè)線程對(duì)象后,并且給線程關(guān)聯(lián)線程函數(shù),該線程就被啟動(dòng),與主線程一起運(yùn)行。線程函數(shù)一般情況下可按照以下三種方式提供:
函數(shù)指針lambda表達(dá)式
函數(shù)對(duì)象
例如:
class add { public:int operator()(int x, int y){return x + y;} };int func(int x, int y) {return x + y; }int main() {int a = 10, b = 20;thread t1(func, a, b); // 函數(shù)指針thread t2([=](int, int)->int {return a + b; }, a, b); // lambda表達(dá)式function<int<int,int>> A = add();thread t3(A,a,b); // 函數(shù)對(duì)象t1.join();t2.join();t3.join();return 0; }thread類是防拷貝的,不允許拷貝構(gòu)造以及賦值,但是可以移動(dòng)構(gòu)造和移動(dòng)賦值,即將一個(gè)線程對(duì)象關(guān)聯(lián)線程的狀態(tài)轉(zhuǎn)移給其他線程對(duì)象,轉(zhuǎn)移期間不意向線程的執(zhí)行。
可以通過jionable()函數(shù)判斷線程是否是有效的,如果是以下任意情況,則線程無效:
1.采用無參構(gòu)造函數(shù)構(gòu)造的線程對(duì)象
2.線程對(duì)象的狀態(tài)已經(jīng)轉(zhuǎn)移給其他線程對(duì)象
3.線程已經(jīng)調(diào)用jion或者detach結(jié)束
面試題:并發(fā)與并行的區(qū)別?
并發(fā):
當(dāng)存在多個(gè)線程時(shí),若系統(tǒng)僅有一個(gè)CPU,則根本不可能真正地同時(shí)進(jìn)行一個(gè)以上的線程,系統(tǒng)只能把CPU的運(yùn)行時(shí)間劃分為若干個(gè)時(shí)間段,再將時(shí)間段分配給各個(gè)線程。在一個(gè)線程在其時(shí)間段執(zhí)行時(shí),其余線程處于掛起狀。這種方式我們稱之為并發(fā)。并行:
若系統(tǒng)擁有一個(gè)以上CPU時(shí),則存在多個(gè)線程時(shí)可并行執(zhí)行。當(dāng)一個(gè)CPU執(zhí)行一個(gè)線程時(shí),另一個(gè)CPU可以執(zhí)行另一個(gè)線程,兩個(gè)線程互不搶占CPU資源,可以同時(shí)進(jìn)行。這種方式我們稱之為并行。區(qū)別:
并發(fā)和并行是即相似又有區(qū)別的兩個(gè)概念, 并行是指兩個(gè)或者多個(gè)事件在同一時(shí)刻發(fā)生;而并發(fā)是指兩個(gè)或多個(gè)事件在同一時(shí)間間隔內(nèi)發(fā)生。在多道程序環(huán)境下,并發(fā)性是指在一段時(shí)間內(nèi)宏觀上有多個(gè)程序在同時(shí)運(yùn)行,但在單處理機(jī)系統(tǒng)中,每一時(shí)刻卻僅能有一道程序執(zhí)行,故微觀上這些程序只能是分時(shí)地交替執(zhí)行。
7.2. 線程函數(shù)參數(shù)
線程函數(shù)的參數(shù)是以值拷貝的方式拷貝到線程棧空間中的,因此:即使線程參數(shù)為引用類型,在線程中修改后也不能修改外部實(shí)參,因?yàn)槠鋵?shí)際引用的是線程棧中的拷貝,而不是外部實(shí)參。
當(dāng)然還有一種方法能夠改變參數(shù)的值,那就是指針:
注意:如果是類成員函數(shù)作為線程參數(shù)時(shí),必須將this作為線程函數(shù)參數(shù)。
class A { public:int add(int x, int y){cout << x + y << endl;return x + y;} };int main() {A a;// 傳入順序 線程函數(shù),實(shí)例化類指針,函數(shù)參數(shù)thread t1(&A::add, &a, 10, 20);t1.join();return 0; }7.3. lock_guard與unique_lock
在多線程環(huán)境下,如果想要保證某個(gè)變量的安全性,只要將其設(shè)置成對(duì)應(yīng)的原子類型即可,即高效又不容易出現(xiàn)死鎖問題。但是有些情況下,我們可能需要保證一段代碼的安全性,那么就只能通過鎖的方式來進(jìn)行控制。
比如兩個(gè)線程同時(shí)對(duì)一個(gè)變量進(jìn)行++操作:
int x = 0;void add(int n) {for (int i = 0; i < n; ++i){x++;cout << this_thread::get_id() << ":" << x << endl;} }int main() {thread t1(add, 10000);thread t2(add, 10000);t1.join();t2.join();return 0; }上面的代碼兩個(gè)線程各自對(duì)x加了10000次,但是從結(jié)果中可以發(fā)現(xiàn)x最后的值只有1992,所以這其中一定出現(xiàn)了線程安全問題。
所以這里需要進(jìn)行加鎖操作,C++11中也新增了加鎖的庫:
那么這里問題來了,這里的加鎖應(yīng)該加載for循環(huán)的里面還是外面?
答案是外面。雖然加載外面看起來兩個(gè)線程就是串行運(yùn)行了,但是針對(duì)這里的實(shí)際問題而言++的操作是非常快的,如果鎖加在循環(huán)里面會(huì)導(dǎo)致兩個(gè)線程頻繁的去競(jìng)爭(zhēng)鎖和釋放鎖,頻繁的去切換上下文,導(dǎo)致對(duì)資源的消耗非常大。
int x = 0; mutex mtx; void add(int n) {mtx.lock();for (int i = 0; i < n; ++i){//mtx.lock();x++;cout << this_thread::get_id() << ":" << x << endl;//mtx.unlock();}mtx.unlock(); }int main() {thread t1(add, 10000);thread t2(add, 10000);t1.join();t2.join();return 0; }7.3.1. Mutex的種類
上述代碼的缺陷:鎖控制不好時(shí),可能會(huì)造成死鎖,最常見的比如在鎖中間代碼返回,或者在鎖的范圍內(nèi)拋異常。因此:C++11采用RAII的方式對(duì)鎖進(jìn)行了封裝,即lock_guard和unique_lock。
在C++11中,Mutex總共包了四個(gè)互斥量的種類:
mutex:
C++11提供的最基本的互斥量,該類的對(duì)象之間不能拷貝,也不能進(jìn)行移動(dòng)。mutex最常用的三個(gè)函數(shù):
| 函數(shù)名 | 函數(shù)功能 |
| lock() | 上鎖:鎖住互斥量 |
| unlock() | 解鎖:釋放對(duì)互斥量的所有權(quán) |
| try_lock() | 嘗試鎖住互斥量,如果互斥量被其他線程占有,則當(dāng)前線程也不會(huì)被阻塞 |
注意,線程函數(shù)調(diào)用lock()時(shí),可能會(huì)發(fā)生以下三種情況:
- 如果該互斥量當(dāng)前沒有被鎖住,則調(diào)用線程將該互斥量鎖住,直到調(diào)用 unlock之前,該線程一直擁有該鎖
- 如果當(dāng)前互斥量被其他線程鎖住,則當(dāng)前的調(diào)用線程被阻塞住
- 如果當(dāng)前互斥量被當(dāng)前調(diào)用線程鎖住,則會(huì)產(chǎn)生死鎖(deadlock)
線程函數(shù)調(diào)用try_lock()時(shí),可能會(huì)發(fā)生以下三種情況:
- 如果當(dāng)前互斥量沒有被其他線程占有,則該線程鎖住互斥量,直到該線程調(diào)用 unlock 釋放互斥量
- 如果當(dāng)前互斥量被其他線程鎖住,則當(dāng)前調(diào)用線程返回 false,而并不會(huì)被阻塞掉
- 如果當(dāng)前互斥量被當(dāng)前調(diào)用線程鎖住,則會(huì)產(chǎn)生死鎖(deadlock)
recursive_mutex :
其允許同一個(gè)線程對(duì)互斥量多次上鎖(即遞歸上鎖),來獲得對(duì)互斥量對(duì)象的多層所有權(quán),釋放互斥量時(shí)需要調(diào)用與該鎖層次深度相同次數(shù)的 unlock(),除此之外,std::recursive_mutex 的特性和std::mutex 大致相同。
timed_mutex :
比 std::mutex 多了兩個(gè)成員函數(shù),try_lock_for(),try_lock_until() 。
try_lock_for()
接受一個(gè)時(shí)間范圍,表示在這一段時(shí)間范圍之內(nèi)線程如果沒有獲得鎖則被阻塞住(與 std::mutex的 try_lock() 不同,try_lock 如果被調(diào)用時(shí)沒有獲得鎖則直接返回 false),如果在此期間其他線程釋放了鎖,則該線程可以獲得對(duì)互斥量的鎖,如果超時(shí)(即在指定時(shí)間內(nèi)還是沒有獲得鎖),則返回 false。
try_lock_until()
接受一個(gè)時(shí)間點(diǎn)作為參數(shù),在指定時(shí)間點(diǎn)未到來之前線程如果沒有獲得鎖則被阻塞住,如果在此期間其他線程釋放了鎖,則該線程可以獲得對(duì)互斥量的鎖,如果超時(shí)(即在指定時(shí)間內(nèi)還是沒有獲得鎖),則返回 false。
7.3.2. lock_guard
std::lock_gurad 是 C++11 中定義的模板類。定義如下 :
template<class _Mutex> class lock_guard { public:// 在構(gòu)造lock_gard時(shí),_Mtx還沒有被上鎖explicit lock_guard(_Mutex& _Mtx): _MyMutex(_Mtx){_MyMutex.lock();}// 在構(gòu)造lock_gard時(shí),_Mtx已經(jīng)被上鎖,此處不需要再上鎖lock_guard(_Mutex& _Mtx, adopt_lock_t): _MyMutex(_Mtx){}~lock_guard() _NOEXCEPT{_MyMutex.unlock();}lock_guard(const lock_guard&) = delete;lock_guard& operator=(const lock_guard&) = delete; private:_Mutex& _MyMutex; };lock_guard類模板主要是通過RAII的方式,對(duì)其管理的互斥量進(jìn)行了封裝,在需要加鎖的地方,只需要用上述介紹的任意互斥體實(shí)例化一個(gè)lock_guard,調(diào)用構(gòu)造函數(shù)成功上鎖,出作用域前,lock_guard對(duì)象要被銷毀,調(diào)用析構(gòu)函數(shù)自動(dòng)解鎖,可以有效避免死鎖問題。
lock_guard的缺陷:太單一,用戶沒有辦法對(duì)該鎖進(jìn)行控制,因此C++11又提供了unique_lock。
7.3.3. unique_lock
與lock_gard類似,unique_lock*類模板也是采用RAII的方式對(duì)鎖進(jìn)行了封裝,并且也是以獨(dú)占所有權(quán)的方式管理mutex對(duì)象的上鎖和解鎖操作,即其對(duì)象之間不能發(fā)生拷貝。在構(gòu)造(或移動(dòng)(move)賦值)時(shí),unique_lock 對(duì)象需要傳遞一個(gè) Mutex 對(duì)象作為它的參數(shù),新創(chuàng)建的 unique_lock 對(duì)象負(fù)責(zé)傳入的 Mutex對(duì)象的上鎖和解鎖操作。使用以上類型互斥量實(shí)例化unique_lock的對(duì)象時(shí),自動(dòng)調(diào)用構(gòu)造函數(shù)上鎖,unique_lock對(duì)象銷毀時(shí)自動(dòng)調(diào)用析構(gòu)函數(shù)解鎖,可以很方便的防止死鎖問題。
與lock_guard不同的是,unique_lock更加的靈活,提供了更多的成員函數(shù):
上鎖/解鎖操作:lock、try_lock、try_lock_for、try_lock_until和unlock
修改操作:移動(dòng)賦值、交換(swap:與另一個(gè)unique_lock對(duì)象互換所管理的互斥量所有權(quán))、釋放(release:返回它所管理的互斥量對(duì)象的指針,并釋放所有權(quán))
獲取屬性:owns_lock(返回當(dāng)前對(duì)象是否上了鎖)、operator bool()(與owns_lock()的功能相同)、mutex(返回當(dāng)前unique_lock所管理的互斥量的指針)。
7.4. 原子性操作庫(atomic)
多線程最主要的問題是共享數(shù)據(jù)帶來的問題(即線程安全)。如果共享數(shù)據(jù)都是只讀的,那么沒問題,因?yàn)橹蛔x操作不會(huì)影響到數(shù)據(jù),更不會(huì)涉及對(duì)數(shù)據(jù)的修改,所以所有線程都會(huì)獲得同樣的數(shù)據(jù)。但是,當(dāng)一個(gè)或多個(gè)線程要修改共享數(shù)據(jù)時(shí),就會(huì)產(chǎn)生很多潛在的麻煩 。
所謂原子操作:即不可被中斷的一個(gè)或一系列操作,C++11引入的原子操作類型,使得線程間數(shù)據(jù)的同步變得非常高效 。
對(duì)于上面的問題:兩個(gè)線程同時(shí)對(duì)一個(gè)變量進(jìn)行++操作,這里就可以用到原子性操作:
#include<atomic> atomic<int> x = 0; void add(int n) {for (int i = 0; i < n; ++i){x++; // 原子操作cout << this_thread::get_id() << ":" << x << endl;} }int main() {thread t1(add, 10000);thread t2(add, 10000);t1.join();t2.join();return 0; }在C++11中,程序員不需要對(duì)原子類型變量進(jìn)行加鎖解鎖操作,線程能夠?qū)υ宇愋妥兞炕コ獾脑L問。
更為普遍的,程序員可以使用atomic類模板,定義出需要的任意原子類型。
atmoic<T> t; // 聲明一個(gè)類型為T的原子類型變量t注意:原子類型通常屬于"資源型"數(shù)據(jù),多個(gè)線程只能訪問單個(gè)原子類型的拷貝,因此在C++11中,原子類型只能從其模板參數(shù)中進(jìn)行構(gòu)造,不允許原子類型進(jìn)行拷貝構(gòu)造、移動(dòng)構(gòu)造以及operator=等,為了防止意外,標(biāo)準(zhǔn)庫已經(jīng)將atmoic模板類中的拷貝構(gòu)造、移動(dòng)構(gòu)造、賦值運(yùn)算符重載默認(rèn)刪除掉了。
#include <atomic> int main() {atomic<int> a1(0);//atomic<int> a2(a1); // 編譯失敗atomic<int> a2(0);//a2 = a1; // 編譯失敗return 0; }8. 條件變量(condition_variable)
在學(xué)習(xí)Linux時(shí)我們學(xué)到過條件變量,在C++11中也增加了條件變量。
問題:如果實(shí)現(xiàn)一個(gè)線程打印偶數(shù),一個(gè)線程打印奇數(shù),且兩個(gè)線程交替打印?
如果我們使用前面學(xué)過的加鎖來試一試:
int main() {int end = 100;int i = 0;mutex mtx;thread t1([end, &i, &mtx] {while (i < end){unique_lock<mutex> lock(mtx);this_thread::sleep_for(std::chrono::milliseconds(100));cout << this_thread::get_id() << "->" << i << endl;++i;}});thread t2([end, &i, &mtx] {while (i < end){unique_lock<mutex> lock(mtx);this_thread::sleep_for(std::chrono::milliseconds(100));cout << this_thread::get_id() << "->" << i << endl;++i;}});t1.join();t2.join();return 0; }通過結(jié)果我們可以看到,上面的方法根本不能實(shí)現(xiàn)交替打印奇偶數(shù),甚至一個(gè)線程打印了多次。
所以這里可以使用互斥鎖+條件變量解決:條件變量可以因?yàn)橐粋€(gè)條件的不滿足而使線程陷入休眠狀態(tài),并且釋放已經(jīng)申請(qǐng)到的鎖,直到條件滿足才會(huì)喚醒線程。
注意:Predicate是一個(gè)可調(diào)用的對(duì)象或函數(shù),他的結(jié)果會(huì)決定線程是否進(jìn)入等待,具體實(shí)現(xiàn)為下圖所示,若while條件成立,會(huì)去調(diào)用wait函數(shù)。
這里條件變量停止等待的條件時(shí)pred為真。
#include<condition_variable> int main() {int end = 100;int i = 0;mutex mtx;condition_variable cv;bool flag = false;//打印偶數(shù)thread t1([end, &i, &mtx,&cv,&flag] {while (i < end){unique_lock<mutex> lock(mtx);// flag=false,返回true,t1不會(huì)等待cv.wait(lock, [&flag] {return !flag; });this_thread::sleep_for(std::chrono::milliseconds(100));cout << this_thread::get_id() << "->" << i << endl;++i;flag = true;cv.notify_one();}});//打印奇數(shù)thread t2([end, &i, &mtx, &cv, &flag] {while (i < end){unique_lock<mutex> lock(mtx);// flag = false,返回false,t2會(huì)等待,如果申請(qǐng)到了鎖也會(huì)釋放cv.wait(lock, [&flag] {return flag; });this_thread::sleep_for(std::chrono::milliseconds(100));cout << this_thread::get_id() << "->" << i << endl;++i;flag = false;cv.notify_one();}});t1.join();t2.join();return 0; }總結(jié)
以上是生活随笔為你收集整理的【C++】C++11 新特性的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 新IT引领新经济 新华三惠州云博会展现“
- 下一篇: C++ Primer Plus_读书笔记