小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

C++11常用新特性快速一覽

 蘭亭文藝 2020-02-06

最近工作中,遇到一些問(wèn)題,使用C++11實(shí)現(xiàn)起來(lái)會(huì)更加方便,而線上的生產(chǎn)環(huán)境還不支持C++11,于是決定新年開(kāi)工后,在組內(nèi)把C++11推廣開(kāi)來(lái),整理以下文檔,方便自己查閱,也方便同事快速上手。(對(duì)于異步編程十分實(shí)用的Future/Promise以及智能指針等,將不做整理介紹,組內(nèi)使用的框架已經(jīng)支持并廣泛使用了,用的是自己公司參考boost實(shí)現(xiàn)的版本)

1. nullptr

nullptr 出現(xiàn)的目的是為了替代 NULL。

在某種意義上來(lái)說(shuō),傳統(tǒng) C++ 會(huì)把 NULL、0 視為同一種東西,這取決于編譯器如何定義 NULL,有些編譯器會(huì)將 NULL 定義為 ((void*)0),有些則會(huì)直接將其定義為 0

C++ 不允許直接將 void * 隱式轉(zhuǎn)換到其他類(lèi)型,但如果 NULL 被定義為 ((void*)0),那么當(dāng)編譯char *ch = NULL;時(shí),NULL 只好被定義為 0。

而這依然會(huì)產(chǎn)生問(wèn)題,將導(dǎo)致了 C++ 中重載特性會(huì)發(fā)生混亂,考慮:

void foo(char *);

void foo(int);

對(duì)于這兩個(gè)函數(shù)來(lái)說(shuō),如果 NULL 又被定義為了 0 那么 foo(NULL); 這個(gè)語(yǔ)句將會(huì)去調(diào)用 foo(int),從而導(dǎo)致代碼違反直觀。

為了解決這個(gè)問(wèn)題,C++11 引入了 nullptr 關(guān)鍵字,專(zhuān)門(mén)用來(lái)區(qū)分空指針、0。

nullptr 的類(lèi)型為 nullptr_t,能夠隱式的轉(zhuǎn)換為任何指針或成員指針的類(lèi)型,也能和他們進(jìn)行相等或者不等的比較。

當(dāng)需要使用 NULL 時(shí)候,養(yǎng)成直接使用 nullptr的習(xí)慣。

2. 類(lèi)型推導(dǎo)

C++11 引入了 auto decltype 這兩個(gè)關(guān)鍵字實(shí)現(xiàn)了類(lèi)型推導(dǎo),讓編譯器來(lái)操心變量的類(lèi)型。

auto

auto 在很早以前就已經(jīng)進(jìn)入了 C++,但是他始終作為一個(gè)存儲(chǔ)類(lèi)型的指示符存在,與 register 并存。在傳統(tǒng) C++ 中,如果一個(gè)變量沒(méi)有聲明為 register 變量,將自動(dòng)被視為一個(gè) auto 變量。而隨著 register 被棄用,對(duì) auto 的語(yǔ)義變更也就非常自然了。

使用 auto 進(jìn)行類(lèi)型推導(dǎo)的一個(gè)最為常見(jiàn)而且顯著的例子就是迭代器。在以前我們需要這樣來(lái)書(shū)寫(xiě)一個(gè)迭代器:

for(vector<int>::const_iterator itr = vec.cbegin(); itr != vec.cend(); ++itr)

而有了 auto 之后可以:

// 由于 cbegin() 將返回 vector<int>::const_iterator

// 所以 itr 也應(yīng)該是 vector<int>::const_iterator 類(lèi)型

for(auto itr = vec.cbegin(); itr != vec.cend(); ++itr);

一些其他的常見(jiàn)用法:

auto i = 5;             // i 被推導(dǎo)為 int

auto arr = new auto(10) // arr 被推導(dǎo)為 int *

注意:auto 不能用于函數(shù)傳參,因此下面的做法是無(wú)法通過(guò)編譯的(考慮重載的問(wèn)題,我們應(yīng)該使用模板):

int add(auto x, auto y);

此外,auto 還不能用于推導(dǎo)數(shù)組類(lèi)型:

#include <iostream>

int main() {

 auto i = 5;

 int arr[10] = {0};

 auto auto_arr = arr;

 auto auto_arr2[10] = arr;

 return 0;

}

decltype

decltype 關(guān)鍵字是為了解決 auto 關(guān)鍵字只能對(duì)變量進(jìn)行類(lèi)型推導(dǎo)的缺陷而出現(xiàn)的。它的用法和 sizeof 很相似:

decltype(表達(dá)式)

1

在此過(guò)程中,編譯器分析表達(dá)式并得到它的類(lèi)型,卻不實(shí)際計(jì)算表達(dá)式的值。

有時(shí)候,我們可能需要計(jì)算某個(gè)表達(dá)式的類(lèi)型,例如:

auto x = 1;

auto y = 2;

decltype(x+y) z;

拖尾返回類(lèi)型、auto decltype 配合

你可能會(huì)思考,auto 能不能用于推導(dǎo)函數(shù)的返回類(lèi)型。考慮這樣一個(gè)例子加法函數(shù)的例子,在傳統(tǒng) C++ 中我們必須這么寫(xiě):

template<typename R, typename T, typename U>

R add(T x, U y) {

    return x+y

}

這樣的代碼其實(shí)變得很丑陋,因?yàn)槌绦騿T在使用這個(gè)模板函數(shù)的時(shí)候,必須明確指出返回類(lèi)型。但事實(shí)上我們并不知道 add() 這個(gè)函數(shù)會(huì)做什么樣的操作,獲得一個(gè)什么樣的返回類(lèi)型。

C++11 中這個(gè)問(wèn)題得到解決。雖然你可能馬上回反應(yīng)出來(lái)使用 decltype 推導(dǎo) x+y 的類(lèi)型,寫(xiě)出這樣的代碼:

decltype(x+y) add(T x, U y);

1

但事實(shí)上這樣的寫(xiě)法并不能通過(guò)編譯。這是因?yàn)樵诰幾g器讀到 decltype(x+y) 時(shí),x y 尚未被定義。為了解決這個(gè)問(wèn)題,C++11 還引入了一個(gè)叫做拖尾返回類(lèi)型(trailing return type),利用 auto 關(guān)鍵字將返回類(lèi)型后置:

template<typename T, typename U>

auto add(T x, U y) -> decltype(x+y) {

    return x+y;

}

C++14 開(kāi)始是可以直接讓普通函數(shù)具備返回值推導(dǎo),因此下面的寫(xiě)法變得合法:

template<typename T, typename U>

auto add(T x, U y) {

    return x+y;

}

3. 區(qū)間迭代

基于范圍的 for 循環(huán)

C++11 引入了基于范圍的迭代寫(xiě)法,我們擁有了能夠?qū)懗鱿?/span> Python 一樣簡(jiǎn)潔的循環(huán)語(yǔ)句。

最常用的 std::vector 遍歷將從原來(lái)的樣子:

std::vector<int> arr(5, 100);

for(std::vector<int>::iterator i = arr.begin(); i != arr.end(); ++i) {

    std::cout << *i << std::endl;

}

變得非常的簡(jiǎn)單:

// & 啟用了引用

for(auto &i : arr) {   

    std::cout << i << std::endl;

}

4. 初始化列表

C++11 提供了統(tǒng)一的語(yǔ)法來(lái)初始化任意的對(duì)象,例如:

struct A {

    int a;

    float b;

};

struct B {

    B(int _a, float _b): a(_a), b(_b) {}

private:

    int a;

    float b;

};

A a {1, 1.1};    // 統(tǒng)一的初始化語(yǔ)法

B b {2, 2.2};

C++11 還把初始化列表的概念綁定到了類(lèi)型上,并將其稱(chēng)之為 std::initializer_list,允許構(gòu)造函數(shù)或其他函數(shù)像參數(shù)一樣使用初始化列表,這就為類(lèi)對(duì)象的初始化與普通數(shù)組和 POD 的初始化方法提供了統(tǒng)一的橋梁,例如:

#include <initializer_list>

class Magic {

public:

    Magic(std::initializer_list<int> list) {}

};

Magic magic = {1,2,3,4,5};

std::vector<int> v = {1, 2, 3, 4};

5. 模板增強(qiáng)

外部模板

傳統(tǒng) C++ 中,模板只有在使用時(shí)才會(huì)被編譯器實(shí)例化。只要在每個(gè)編譯單元(文件)中編譯的代碼中遇到了被完整定義的模板,都會(huì)實(shí)例化。這就產(chǎn)生了重復(fù)實(shí)例化而導(dǎo)致的編譯時(shí)間的增加。并且,我們沒(méi)有辦法通知編譯器不要觸發(fā)模板實(shí)例化。

C++11 引入了外部模板,擴(kuò)充了原來(lái)的強(qiáng)制編譯器在特定位置實(shí)例化模板的語(yǔ)法,使得能夠顯式的告訴編譯器何時(shí)進(jìn)行模板的實(shí)例化:

template class std::vector<bool>;            // 強(qiáng)行實(shí)例化

extern template class std::vector<double>;  // 不在該編譯文件中實(shí)例化模板

尖括號(hào) >

在傳統(tǒng) C++ 的編譯器中,>>一律被當(dāng)做右移運(yùn)算符來(lái)進(jìn)行處理。但實(shí)際上我們很容易就寫(xiě)出了嵌套模板的代碼:

std::vector<std::vector<int>> wow;

這在傳統(tǒng)C++編譯器下是不能夠被編譯的,而 C++11 開(kāi)始,連續(xù)的右尖括號(hào)將變得合法,并且能夠順利通過(guò)編譯。

類(lèi)型別名模板

在傳統(tǒng) C++中,typedef 可以為類(lèi)型定義一個(gè)新的名稱(chēng),但是卻沒(méi)有辦法為模板定義一個(gè)新的名稱(chēng)。因?yàn)?,模板不是?lèi)型。例如:

template< typename T, typename U, int value>

class SuckType {

public:

    T a;

    U b;

    SuckType():a(value),b(value){}

};

template< typename U>

typedef SuckType<std::vector<int>, U, 1> NewType; // 不合法

C++11 使用 using 引入了下面這種形式的寫(xiě)法,并且同時(shí)支持對(duì)傳統(tǒng) typedef 相同的功效:

template <typename T>

using NewType = SuckType<int, T, 1>;    // 合法

默認(rèn)模板參數(shù)

我們可能定義了一個(gè)加法函數(shù):

template<typename T, typename U>

auto add(T x, U y) -> decltype(x+y) {

    return x+y

}

但在使用時(shí)發(fā)現(xiàn),要使用 add,就必須每次都指定其模板參數(shù)的類(lèi)型。

C++11 中提供了一種便利,可以指定模板的默認(rèn)參數(shù):

template<typename T = int, typename U = int>

auto add(T x, U y) -> decltype(x+y) {

    return x+y;

}

6. 構(gòu)造函數(shù)

委托構(gòu)造

C++11 引入了委托構(gòu)造的概念,這使得構(gòu)造函數(shù)可以在同一個(gè)類(lèi)中一個(gè)構(gòu)造函數(shù)調(diào)用另一個(gè)構(gòu)造函數(shù),從而達(dá)到簡(jiǎn)化代碼的目的:

class Base {

public:

    int value1;

    int value2;

    Base() {

        value1 = 1;

    }

    Base(int value) : Base() {  // 委托 Base() 構(gòu)造函數(shù)

        value2 = 2;

    }

};

繼承構(gòu)造

在繼承體系中,如果派生類(lèi)想要使用基類(lèi)的構(gòu)造函數(shù),需要在構(gòu)造函數(shù)中顯式聲明。

假若基類(lèi)擁有為數(shù)眾多的不同版本的構(gòu)造函數(shù),這樣,在派生類(lèi)中得寫(xiě)很多對(duì)應(yīng)的“透?jìng)鳌睒?gòu)造函數(shù)。如下:

struct A

{

  A(int i) {}

  A(double d,int i){}

  A(float f,int i,const char* c){}

  //...等等系列的構(gòu)造函數(shù)版本

};

struct B:A

{

  B(int i):A(i){}

  B(double d,int i):A(d,i){}

  B(folat f,int i,const char* c):A(f,i,e){}

  //......等等好多個(gè)和基類(lèi)構(gòu)造函數(shù)對(duì)應(yīng)的構(gòu)造函數(shù)

};

C++11的繼承構(gòu)造:

struct A

{

  A(int i) {}

  A(double d,int i){}

  A(float f,int i,const char* c){}

  //...等等系列的構(gòu)造函數(shù)版本

};

struct B:A

{

  using A::A;

  //關(guān)于基類(lèi)各構(gòu)造函數(shù)的繼承一句話搞定

  //......

};

如果一個(gè)繼承構(gòu)造函數(shù)不被相關(guān)的代碼使用,編譯器不會(huì)為之產(chǎn)生真正的函數(shù)代碼,這樣比透?jìng)骰?lèi)各種構(gòu)造函數(shù)更加節(jié)省目標(biāo)代碼空間。

7. Lambda 表達(dá)式

Lambda 表達(dá)式,實(shí)際上就是提供了一個(gè)類(lèi)似匿名函數(shù)的特性,而匿名函數(shù)則是在需要一個(gè)函數(shù),但是又不想費(fèi)力去命名一個(gè)函數(shù)的情況下去使用的。

Lambda 表達(dá)式的基本語(yǔ)法如下:

[ caputrue ] ( params ) opt -> ret { body; };

1

1) capture是捕獲列表;

2) params是參數(shù)表;(選填)

3) opt是函數(shù)選項(xiàng);可以填mutable,exception,attribute(選填)

mutable說(shuō)明lambda表達(dá)式體內(nèi)的代碼可以修改被捕獲的變量,并且可以訪問(wèn)被捕獲的對(duì)象的non-const方法。

exception說(shuō)明lambda表達(dá)式是否拋出異常以及何種異常。

attribute用來(lái)聲明屬性。

4) ret是返回值類(lèi)型(拖尾返回類(lèi)型)。(選填)

5) body是函數(shù)體。

捕獲列表:lambda表達(dá)式的捕獲列表精細(xì)控制了lambda表達(dá)式能夠訪問(wèn)的外部變量,以及如何訪問(wèn)這些變量。

1) []不捕獲任何變量。

2) [&]捕獲外部作用域中所有變量,并作為引用在函數(shù)體中使用(按引用捕獲)。

3) [=]捕獲外部作用域中所有變量,并作為副本在函數(shù)體中使用(按值捕獲)。注意值捕獲的前提是變量可以拷貝,且被捕獲的變量在 lambda 表達(dá)式被創(chuàng)建時(shí)拷貝,而非調(diào)用時(shí)才拷貝。如果希望lambda表達(dá)式在調(diào)用時(shí)能即時(shí)訪問(wèn)外部變量,我們應(yīng)當(dāng)使用引用方式捕獲。

int a = 0;

auto f = [=] { return a; };

a+=1;

cout << f() << endl;       //輸出0

int a = 0;

auto f = [&a] { return a; };

a+=1;

cout << f() <<endl;       //輸出1

4) [=,&foo]按值捕獲外部作用域中所有變量,并按引用捕獲foo變量。

5) [bar]按值捕獲bar變量,同時(shí)不捕獲其他變量。

6) [this]捕獲當(dāng)前類(lèi)中的this指針,讓lambda表達(dá)式擁有和當(dāng)前類(lèi)成員函數(shù)同樣的訪問(wèn)權(quán)限。如果已經(jīng)使用了&或者=,就默認(rèn)添加此選項(xiàng)。捕獲this的目的是可以在lamda中使用當(dāng)前類(lèi)的成員函數(shù)和成員變量。

class A

{

 public:

     int i_ = 0;

     void func(int x,int y){

         auto x1 = [] { return i_; };                   //error,沒(méi)有捕獲外部變量

         auto x2 = [=] { return i_ + x + y; };          //OK

         auto x3 = [&] { return i_ + x + y; };        //OK

         auto x4 = [this] { return i_; };               //OK

         auto x5 = [this] { return i_ + x + y; };       //error,沒(méi)有捕獲x,y

         auto x6 = [this, x, y] { return i_ + x + y; };     //OK

         auto x7 = [this] { return i_++; };             //OK

};

int a=0 , b=1;

auto f1 = [] { return a; };                         //error,沒(méi)有捕獲外部變量   

auto f2 = [&] { return a++ };                      //OK

auto f3 = [=] { return a; };                        //OK

auto f4 = [=] {return a++; };                       //error,a是以復(fù)制方式捕獲的,無(wú)法修改

auto f5 = [a] { return a+b; };                      //error,沒(méi)有捕獲變量b

auto f6 = [a, &b] { return a + (b++); };                //OK

auto f7 = [=, &b] { return a + (b++); };                //OK

注意f4,雖然按值捕獲的變量值均復(fù)制一份存儲(chǔ)在lambda表達(dá)式變量中,修改他們也并不會(huì)真正影響到外部,但我們卻仍然無(wú)法修改它們。如果希望去修改按值捕獲的外部變量,需要顯示指明lambda表達(dá)式為mutable。被mutable修飾的lambda表達(dá)式就算沒(méi)有參數(shù)也要寫(xiě)明參數(shù)列表。

原因:lambda表達(dá)式可以說(shuō)是就地定義仿函數(shù)閉包的“語(yǔ)法糖”。它的捕獲列表捕獲住的任何外部變量,最終會(huì)變?yōu)殚]包類(lèi)型的成員變量。按照C++標(biāo)準(zhǔn),lambda表達(dá)式的operator()默認(rèn)是const的,一個(gè)const成員函數(shù)是無(wú)法修改成員變量的值的。而mutable的作用,就在于取消operator()const。

int a = 0;

auto f1 = [=] { return a++; };                //error

auto f2 = [=] () mutable { return a++; };       //OK

lambda表達(dá)式的大致原理:每當(dāng)你定義一個(gè)lambda表達(dá)式后,編譯器會(huì)自動(dòng)生成一個(gè)匿名類(lèi)(這個(gè)類(lèi)重載了()運(yùn)算符),我們稱(chēng)為閉包類(lèi)型(closure type)。那么在運(yùn)行時(shí),這個(gè)lambda表達(dá)式就會(huì)返回一個(gè)匿名的閉包實(shí)例,是一個(gè)右值。所以,我們上面的lambda表達(dá)式的結(jié)果就是一個(gè)個(gè)閉包。對(duì)于復(fù)制傳值捕捉方式,類(lèi)中會(huì)相應(yīng)添加對(duì)應(yīng)類(lèi)型的非靜態(tài)數(shù)據(jù)成員。在運(yùn)行時(shí),會(huì)用復(fù)制的值初始化這些成員變量,從而生成閉包。對(duì)于引用捕獲方式,無(wú)論是否標(biāo)記mutable,都可以在lambda表達(dá)式中修改捕獲的值。至于閉包類(lèi)中是否有對(duì)應(yīng)成員,C++標(biāo)準(zhǔn)中給出的答案是:不清楚的,與具體實(shí)現(xiàn)有關(guān)。

lambda表達(dá)式是不能被賦值的:

auto a = [] { cout << "A" << endl; };

auto b = [] { cout << "B" << endl; };

a = b;   // 非法,lambda無(wú)法賦值

auto c = a;   // 合法,生成一個(gè)副本

閉包類(lèi)型禁用了賦值操作符,但是沒(méi)有禁用復(fù)制構(gòu)造函數(shù),所以你仍然可以用一個(gè)lambda表達(dá)式去初始化另外一個(gè)lambda表達(dá)式而產(chǎn)生副本。

在多種捕獲方式中,最好不要使用[=][&]默認(rèn)捕獲所有變量。

默認(rèn)引用捕獲所有變量,你有很大可能會(huì)出現(xiàn)懸掛引用(Dangling references),因?yàn)橐貌东@不會(huì)延長(zhǎng)引用的變量的生命周期:

std::function<int(int)> add_x(int x)

{

    return [&](int a) { return x + a; };

}

上面函數(shù)返回了一個(gè)lambda表達(dá)式,參數(shù)x僅是一個(gè)臨時(shí)變量,函數(shù)add_x調(diào)用后就被銷(xiāo)毀了,但是返回的lambda表達(dá)式卻引用了該變量,當(dāng)調(diào)用這個(gè)表達(dá)式時(shí),引用的是一個(gè)垃圾值,會(huì)產(chǎn)生沒(méi)有意義的結(jié)果。上面這種情況,使用默認(rèn)傳值方式可以避免懸掛引用問(wèn)題。

但是采用默認(rèn)值捕獲所有變量仍然有風(fēng)險(xiǎn),看下面的例子:

class Filter

{

public:

    Filter(int divisorVal):

        divisor{divisorVal}

    {}

    std::function<bool(int)> getFilter()

    {

        return [=](int value) {return value % divisor == 0; };

    }

private:

    int divisor;

};

這個(gè)類(lèi)中有一個(gè)成員方法,可以返回一個(gè)lambda表達(dá)式,這個(gè)表達(dá)式使用了類(lèi)的數(shù)據(jù)成員divisor。而且采用默認(rèn)值方式捕捉所有變量。你可能認(rèn)為這個(gè)lambda表達(dá)式也捕捉了divisor的一份副本,但是實(shí)際上并沒(méi)有。因?yàn)閿?shù)據(jù)成員divisor對(duì)lambda表達(dá)式并不可見(jiàn),你可以用下面的代碼驗(yàn)證:

// 類(lèi)的方法,下面無(wú)法編譯,因?yàn)?/span>divisor并不在lambda捕捉的范圍

std::function<bool(int)> getFilter()

{

    return [divisor](int value) {return value % divisor == 0; };

}

原代碼中,lambda表達(dá)式實(shí)際上捕捉的是this指針的副本,所以原來(lái)的代碼等價(jià)于:

std::function<bool(int)> getFilter()

{

    return [this](int value) {return value % this->divisor == 0; };

}

盡管還是以值方式捕獲,但是捕獲的是指針,其實(shí)相當(dāng)于以引用的方式捕獲了當(dāng)前類(lèi)對(duì)象,所以lambda表達(dá)式的閉包與一個(gè)類(lèi)對(duì)象綁定在一起了,這很危險(xiǎn),因?yàn)槟闳匀挥锌赡茉陬?lèi)對(duì)象析構(gòu)后使用這個(gè)lambda表達(dá)式,那么類(lèi)似“懸掛引用”的問(wèn)題也會(huì)產(chǎn)生。所以,采用默認(rèn)值捕捉所有變量仍然是不安全的,主要是由于指針變量的復(fù)制,實(shí)際上還是按引用傳值。

lambda表達(dá)式可以賦值給對(duì)應(yīng)類(lèi)型的函數(shù)指針。但是使用函數(shù)指針并不是那么方便。所以STL定義在< functional >頭文件提供了一個(gè)多態(tài)的函數(shù)對(duì)象封裝std::function,其類(lèi)似于函數(shù)指針。它可以綁定任何類(lèi)函數(shù)對(duì)象,只要參數(shù)與返回類(lèi)型相同。如下面的返回一個(gè)bool且接收兩個(gè)int的函數(shù)包裝器:

std::function<bool(int, int)> wrapper = [](int x, int y) { return x < y; };

1

lambda表達(dá)式一個(gè)更重要的應(yīng)用是其可以用于函數(shù)的參數(shù),通過(guò)這種方式可以實(shí)現(xiàn)回調(diào)函數(shù)。

最常用的是在STL算法中,比如你要統(tǒng)計(jì)一個(gè)數(shù)組中滿足特定條件的元素?cái)?shù)量,通過(guò)lambda表達(dá)式給出條件,傳遞給count_if函數(shù):

int value = 3;

vector<int> v {1, 3, 5, 2, 6, 10};

int count = std::count_if(v.beigin(), v.end(), [value](int x) { return x > value; });

再比如你想生成斐波那契數(shù)列,然后保存在數(shù)組中,此時(shí)你可以使用generate函數(shù),并輔助lambda表達(dá)式:

vector<int> v(10);

int a = 0;

int b = 1;

std::generate(v.begin(), v.end(), [&a, &b] { int value = b; b = b + a; a = value; return value; });

// 此時(shí)v {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}

當(dāng)需要遍歷容器并對(duì)每個(gè)元素進(jìn)行操作時(shí):

std::vector<int> v = { 1, 2, 3, 4, 5, 6 };

int even_count = 0;

for_each(v.begin(), v.end(), [&even_count](int val){

    if(!(val & 1)){

        ++ even_count;

    }

});

std::cout << "The number of even is " << even_count << std::endl;

大部分STL算法,可以非常靈活地搭配lambda表達(dá)式來(lái)實(shí)現(xiàn)想要的效果。

8. 新增容器

std::array

std::array 保存在棧內(nèi)存中,相比堆內(nèi)存中的 std::vector,我們能夠靈活的訪問(wèn)這里面的元素,從而獲得更高的性能。

std::array 會(huì)在編譯時(shí)創(chuàng)建一個(gè)固定大小的數(shù)組,std::array 不能夠被隱式的轉(zhuǎn)換成指針,使用 std::array只需指定其類(lèi)型和大小即可:

std::array<int, 4> arr= {1,2,3,4};

int len = 4;

std::array<int, len> arr = {1,2,3,4}; // 非法, 數(shù)組大小參數(shù)必須是常量表達(dá)式

當(dāng)我們開(kāi)始用上了 std::array 時(shí),難免會(huì)遇到要將其兼容 C 風(fēng)格的接口,這里有三種做法:

void foo(int *p, int len) {

    return;

}

std::array<int 4> arr = {1,2,3,4};

// C 風(fēng)格接口傳參

// foo(arr, arr.size());           // 非法, 無(wú)法隱式轉(zhuǎn)換

foo(&arr[0], arr.size());

foo(arr.data(), arr.size());

// 使用 `std::sort`

std::sort(arr.begin(), arr.end());

std::forward_list

std::forward_list 是一個(gè)列表容器,使用方法和 std::list 基本類(lèi)似。

std::list 的雙向鏈表的實(shí)現(xiàn)不同,std::forward_list 使用單向鏈表進(jìn)行實(shí)現(xiàn),提供了 O(1) 復(fù)雜度的元素插入,不支持快速隨機(jī)訪問(wèn)(這也是鏈表的特點(diǎn)),也是標(biāo)準(zhǔn)庫(kù)容器中唯一一個(gè)不提供 size() 方法的容器。當(dāng)不需要雙向迭代時(shí),具有比 std::list 更高的空間利用率。

無(wú)序容器

C++11 引入了兩組無(wú)序容器:

std::unordered_map/std::unordered_multimap std::unordered_set/std::unordered_multiset。

無(wú)序容器中的元素是不進(jìn)行排序的,內(nèi)部通過(guò) Hash 表實(shí)現(xiàn),插入和搜索元素的平均復(fù)雜度為 O(constant)。

元組 std::tuple

元組的使用有三個(gè)核心的函數(shù):

std::make_tuple: 構(gòu)造元組

std::get: 獲得元組某個(gè)位置的值

std::tie: 元組拆包

#include <tuple>

#include <iostream>

auto get_student(int id)

{

    // 返回類(lèi)型被推斷為 std::tuple<double, char, std::string>

    if (id == 0)

        return std::make_tuple(3.8, 'A', "張三");

    if (id == 1)

        return std::make_tuple(2.9, 'C', "李四");

    if (id == 2)

        return std::make_tuple(1.7, 'D', "王五");

    return std::make_tuple(0.0, 'D', "null");  

    // 如果只寫(xiě) 0 會(huì)出現(xiàn)推斷錯(cuò)誤, 編譯失敗

}

int main()

{

    auto student = get_student(0);

    std::cout << "ID: 0, "

    << "GPA: " << std::get<0>(student) << ", "

    << "成績(jī): " << std::get<1>(student) << ", "

    << "姓名: " << std::get<2>(student) << '\n';

    double gpa;

    char grade;

    std::string name;

    // 元組進(jìn)行拆包

    std::tie(gpa, grade, name) = get_student(1);

    std::cout << "ID: 1, "

    << "GPA: " << gpa << ", "

    << "成績(jī): " << grade << ", "

    << "姓名: " << name << '\n';

}

合并兩個(gè)元組,可以通過(guò) std::tuple_cat 來(lái)實(shí)現(xiàn)。

auto new_tuple = std::tuple_cat(get_student(1), std::move(t));

9. 正則表達(dá)式

正則表達(dá)式描述了一種字符串匹配的模式。一般使用正則表達(dá)式主要是實(shí)現(xiàn)下面三個(gè)需求:

1) 檢查一個(gè)串是否包含某種形式的子串;

2) 將匹配的子串替換;

3) 從某個(gè)串中取出符合條件的子串。

C++11 提供的正則表達(dá)式庫(kù)操作 std::string 對(duì)象,對(duì)模式 std::regex (本質(zhì)是 std::basic_regex)進(jìn)行初始化,通過(guò) std::regex_match 進(jìn)行匹配,從而產(chǎn)生 std::smatch (本質(zhì)是 std::match_results 對(duì)象)。

我們通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)簡(jiǎn)單介紹這個(gè)庫(kù)的使用??紤]下面的正則表達(dá)式:

[a-z]+.txt: 在這個(gè)正則表達(dá)式中, [a-z] 表示匹配一個(gè)小寫(xiě)字母, + 可以使前面的表達(dá)式匹配多次,因此 [a-z]+ 能夠匹配一個(gè)及以上小寫(xiě)字母組成的字符串。在正則表達(dá)式中一個(gè) . 表示匹配任意字符,而 . 轉(zhuǎn)義后則表示匹配字符 . ,最后的 txt 表示嚴(yán)格匹配 txt 這三個(gè)字母。因此這個(gè)正則表達(dá)式的所要匹配的內(nèi)容就是文件名為純小寫(xiě)字母的文本文件。

std::regex_match 用于匹配字符串和正則表達(dá)式,有很多不同的重載形式。最簡(jiǎn)單的一個(gè)形式就是傳入std::string 以及一個(gè) std::regex 進(jìn)行匹配,當(dāng)匹配成功時(shí),會(huì)返回 true,否則返回 false。例如:

#include <iostream>

#include <string>

#include <regex>

int main() {

    std::string fnames[] = {"foo.txt", "bar.txt", "test", "a0.txt", "AAA.txt"};

    // C++ `\` 會(huì)被作為字符串內(nèi)的轉(zhuǎn)義符,為使 `\.` 作為正則表達(dá)式傳遞進(jìn)去生效,需要對(duì) `\` 進(jìn)行二次轉(zhuǎn)義,從而有 `\\.`

    std::regex txt_regex("[a-z]+\\.txt");

    for (const auto &fname: fnames)

        std::cout << fname << ": " << std::regex_match(fname, txt_regex) << std::endl;

}

另一種常用的形式就是依次傳入 std::string/std::smatch/std::regex 三個(gè)參數(shù),其中 std::smatch 的本質(zhì)其實(shí)是 std::match_results,在標(biāo)準(zhǔn)庫(kù)中, std::smatch 被定義為了 std::match_results,也就是一個(gè)子串迭代器類(lèi)型的 match_results。使用 std::smatch 可以方便的對(duì)匹配的結(jié)果進(jìn)行獲取,例如:

std::regex base_regex("([a-z]+)\\.txt");

std::smatch base_match;

for(const auto &fname: fnames) {

    if (std::regex_match(fname, base_match, base_regex)) {

        // sub_match 的第一個(gè)元素匹配整個(gè)字符串

        // sub_match 的第二個(gè)元素匹配了第一個(gè)括號(hào)表達(dá)式

        if (base_match.size() == 2) {

            std::string base = base_match[1].str();

            std::cout << "sub-match[0]: " << base_match[0].str() << std::endl;

            std::cout << fname << " sub-match[1]: " << base << std::endl;

        }

    }

}

以上兩個(gè)代碼段的輸出結(jié)果為:

foo.txt: 1

bar.txt: 1

test: 0

a0.txt: 0

AAA.txt: 0

sub-match[0]: foo.txt

foo.txt sub-match[1]: foo

sub-match[0]: bar.txt

bar.txt sub-match[1]: bar

10. 語(yǔ)言級(jí)線程支持

std::thread

std::mutex/std::unique_lock

std::future/std::packaged_task

std::condition_variable

代碼編譯需要使用 -pthread 選項(xiàng)

11. 右值引用和move語(yǔ)義

先看一個(gè)簡(jiǎn)單的例子直觀感受下:

string a(x);                                    // line 1

string b(x + y);                                    // line 2

string c(some_function_returning_a_string());       // line 3

如果使用以下拷貝構(gòu)造函數(shù):

string(const string& that)

{

    size_t size = strlen(that.data) + 1;

    data = new char[size];

    memcpy(data, that.data, size);

}

以上3行中,只有第一行(line 1)x深度拷貝是有必要的,因?yàn)槲覀兛赡軙?huì)在后邊用到xx是一個(gè)左值(lvalues)。

第二行和第三行的參數(shù)則是右值,因?yàn)楸磉_(dá)式產(chǎn)生的string對(duì)象是匿名對(duì)象,之后沒(méi)有辦法再使用了。

C++ 11引入了一種新的機(jī)制叫做“右值引用”,以便我們通過(guò)重載直接使用右值參數(shù)。我們所要做的就是寫(xiě)一個(gè)以右值引用為參數(shù)的構(gòu)造函數(shù):

string(string&& that)   // string&& is an rvalue reference to a string

{

data = that.data;

that.data = 0;

}

我們沒(méi)有深度拷貝堆內(nèi)存中的數(shù)據(jù),而是僅僅復(fù)制了指針,并把源對(duì)象的指針置空。事實(shí)上,我們“偷取”了屬于源對(duì)象的內(nèi)存數(shù)據(jù)。由于源對(duì)象是一個(gè)右值,不會(huì)再被使用,因此客戶并不會(huì)覺(jué)察到源對(duì)象被改變了。在這里,我們并沒(méi)有真正的復(fù)制,所以我們把這個(gè)構(gòu)造函數(shù)叫做“轉(zhuǎn)移構(gòu)造函數(shù)”(move constructor),他的工作就是把資源從一個(gè)對(duì)象轉(zhuǎn)移到另一個(gè)對(duì)象,而不是復(fù)制他們。

有了右值引用,再來(lái)看看賦值操作符:

string& operator=(string that)

{

std::swap(data, that.data);

return *this;

}

注意到我們是直接對(duì)參數(shù)that傳值,所以that會(huì)像其他任何對(duì)象一樣被初始化,那么確切的說(shuō),that是怎樣被初始化的呢?對(duì)于C++ 98,答案是復(fù)制構(gòu)造函數(shù),但是對(duì)于C++ 11,編譯器會(huì)依據(jù)參數(shù)是左值還是右值在復(fù)制構(gòu)造函數(shù)和轉(zhuǎn)移構(gòu)造函數(shù)間進(jìn)行選擇。

如果是a=b,這樣就會(huì)調(diào)用復(fù)制構(gòu)造函數(shù)來(lái)初始化that(因?yàn)?/span>b是左值),賦值操作符會(huì)與新創(chuàng)建的對(duì)象交換數(shù)據(jù),深度拷貝。這就是copy and swap 慣用法的定義:構(gòu)造一個(gè)副本,與副本交換數(shù)據(jù),并讓副本在作用域內(nèi)自動(dòng)銷(xiāo)毀。這里也一樣。

如果是a = x + y,這樣就會(huì)調(diào)用轉(zhuǎn)移構(gòu)造函數(shù)來(lái)初始化that(因?yàn)?/span>x+y是右值),所以這里沒(méi)有深度拷貝,只有高效的數(shù)據(jù)轉(zhuǎn)移。相對(duì)于參數(shù),that依然是一個(gè)獨(dú)立的對(duì)象,但是他的構(gòu)造函數(shù)是無(wú)用的(trivial),因此堆中的數(shù)據(jù)沒(méi)有必要復(fù)制,而僅僅是轉(zhuǎn)移。沒(méi)有必要復(fù)制他,因?yàn)?/span>x+y是右值,再次,從右值指向的對(duì)象中轉(zhuǎn)移是沒(méi)有問(wèn)題的。

總結(jié)一下:復(fù)制構(gòu)造函數(shù)執(zhí)行的是深度拷貝,因?yàn)樵磳?duì)象本身必須不能被改變。而轉(zhuǎn)移構(gòu)造函數(shù)卻可以復(fù)制指針,把源對(duì)象的指針置空,這種形式下,這是安全的,因?yàn)橛脩舨豢赡茉偈褂眠@個(gè)對(duì)象了。

下面我們進(jìn)一步討論右值引用和move語(yǔ)義。

C++98標(biāo)準(zhǔn)庫(kù)中提供了一種唯一擁有性的智能指針std::auto_ptr,該類(lèi)型在C++11中已被廢棄,因?yàn)槠洹皬?fù)制”行為是危險(xiǎn)的。

auto_ptr<Shape> a(new Triangle);

auto_ptr<Shape> b(a);

注意b是怎樣使用a進(jìn)行初始化的,它不復(fù)制triangle,而是把triangle的所有權(quán)從a傳遞給了b,也可以說(shuō)成“a 被轉(zhuǎn)移進(jìn)了b”或者“triangle被從a轉(zhuǎn)移到了b”。

auto_ptr 的復(fù)制構(gòu)造函數(shù)可能看起來(lái)像這樣(簡(jiǎn)化):

auto_ptr(auto_ptr& source)   // note the missing const

{

p = source.p;

source.p = 0;   // now the source no longer owns the object

}

auto_ptr 的危險(xiǎn)之處在于看上去應(yīng)該是復(fù)制,但實(shí)際上確是轉(zhuǎn)移。調(diào)用被轉(zhuǎn)移過(guò)的auto_ptr 的成員函數(shù)將會(huì)導(dǎo)致不可預(yù)知的后果。所以你必須非常謹(jǐn)慎的使用auto_ptr ,如果他被轉(zhuǎn)移過(guò)。

auto_ptr<Shape> make_triangle()

{

    return auto_ptr<Shape>(new Triangle);

}

auto_ptr<Shape> c(make_triangle());      // move temporary into c

double area = make_triangle()->area();   // perfectly safe

auto_ptr<Shape> a(new Triangle);    // create triangle

auto_ptr<Shape> b(a);               // move a into b

double area = a->area();                // undefined behavior

顯然,在持有auto_ptr 對(duì)象的a表達(dá)式和持有調(diào)用函數(shù)返回的auto_ptr值類(lèi)型的make_triangle()表達(dá)式之間一定有一些潛在的區(qū)別,每調(diào)用一次后者就會(huì)創(chuàng)建一個(gè)新的auto_ptr對(duì)象。這里a 其實(shí)就是一個(gè)左值(lvalue)的例子,而make_triangle()就是右值(rvalue)的例子。

轉(zhuǎn)移像a這樣的左值是非常危險(xiǎn)的,因?yàn)槲覀兛赡苷{(diào)用a的成員函數(shù),這會(huì)導(dǎo)致不可預(yù)知的行為。另一方面,轉(zhuǎn)移像make_triangle()這樣的右值卻是非常安全的,因?yàn)閺?fù)制構(gòu)造函數(shù)之后,我們不能再使用這個(gè)臨時(shí)對(duì)象了,因?yàn)檫@個(gè)轉(zhuǎn)移后的臨時(shí)對(duì)象會(huì)在下一行之前銷(xiāo)毀掉。

我們現(xiàn)在知道轉(zhuǎn)移左值是十分危險(xiǎn)的,但是轉(zhuǎn)移右值卻是很安全的。如果C++能從語(yǔ)言級(jí)別支持區(qū)分左值和右值參數(shù),我就可以完全杜絕對(duì)左值轉(zhuǎn)移,或者把轉(zhuǎn)移左值在調(diào)用的時(shí)候暴露出來(lái),以使我們不會(huì)不經(jīng)意的轉(zhuǎn)移左值。

C++ 11對(duì)這個(gè)問(wèn)題的答案是右值引用。右值引用是針對(duì)右值的新的引用類(lèi)型,語(yǔ)法是X&&。以前的老的引用類(lèi)型X& 現(xiàn)在被稱(chēng)作左值引用。

使用右值引用X&&作為參數(shù)的最有用的函數(shù)之一就是轉(zhuǎn)移構(gòu)造函數(shù)X::X(X&& source),它的主要作用是把源對(duì)象的本地資源轉(zhuǎn)移給當(dāng)前對(duì)象。

C++ 11中,std::auto_ptr< T >已經(jīng)被std::unique_ptr< T >所取代,后者就是利用的右值引用。

其轉(zhuǎn)移構(gòu)造函數(shù):

unique_ptr(unique_ptr&& source)   // note the rvalue reference

{

    ptr = source.ptr;

    source.ptr = nullptr;

}

這個(gè)轉(zhuǎn)移構(gòu)造函數(shù)跟auto_ptr中復(fù)制構(gòu)造函數(shù)做的事情一樣,但是它卻只能接受右值作為參數(shù)。

unique_ptr<Shape> a(new Triangle);

unique_ptr<Shape> b(a);                 // error

unique_ptr<Shape> c(make_triangle());       // okay

第二行不能編譯通過(guò),因?yàn)?/span>a是左值,但是參數(shù)unique_ptr&& source只能接受右值,這正是我們所需要的,杜絕危險(xiǎn)的隱式轉(zhuǎn)移。第三行編譯沒(méi)有問(wèn)題,因?yàn)?/span>make_triangle()是右值,轉(zhuǎn)移構(gòu)造函數(shù)會(huì)將臨時(shí)對(duì)象的所有權(quán)轉(zhuǎn)移給對(duì)象c,這正是我們需要的。

轉(zhuǎn)移左值

有時(shí)候,我們可能想轉(zhuǎn)移左值,也就是說(shuō),有時(shí)候我們想讓編譯器把左值當(dāng)作右值對(duì)待,以便能使用轉(zhuǎn)移構(gòu)造函數(shù),即便這有點(diǎn)不安全。出于這個(gè)目的,C++ 11在標(biāo)準(zhǔn)庫(kù)的頭文件< utility >中提供了一個(gè)模板函數(shù)std::move。實(shí)際上,std::move僅僅是簡(jiǎn)單地將左值轉(zhuǎn)換為右值,它本身并沒(méi)有轉(zhuǎn)移任何東西。它僅僅是讓對(duì)象可以轉(zhuǎn)移。

以下是如何正確的轉(zhuǎn)移左值:

unique_ptr<Shape> a(new Triangle);

unique_ptr<Shape> b(a);              // still an error

unique_ptr<Shape> c(std::move(a));   // okay

請(qǐng)注意,第三行之后,a不再擁有Triangle對(duì)象。不過(guò)這沒(méi)有關(guān)系,因?yàn)橥ㄟ^(guò)明確的寫(xiě)出std::move(a),我們很清楚我們的意圖:親愛(ài)的轉(zhuǎn)移構(gòu)造函數(shù),你可以對(duì)a做任何想要做的事情來(lái)初始化c;我不再需要a了,對(duì)于a,您請(qǐng)自便。

當(dāng)然,如果你在使用了mova(a)之后,還繼續(xù)使用a,那無(wú)疑是搬起石頭砸自己的腳,還是會(huì)導(dǎo)致嚴(yán)重的運(yùn)行錯(cuò)誤。

總之,std::move(some_lvalue)將左值轉(zhuǎn)換為右值(可以理解為一種類(lèi)型轉(zhuǎn)換),使接下來(lái)的轉(zhuǎn)移成為可能。

一個(gè)例子:

class Foo

{

    unique_ptr<Shape> member;

public:

    Foo(unique_ptr<Shape>&& parameter)

    : member(parameter)   // error

    {}

};

上面的parameter,其類(lèi)型是一個(gè)右值引用,只能說(shuō)明parameter是指向右值的引用,而parameter本身是個(gè)左值。(Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: if it has a name, then it is an lvalue. Otherwise, it is an rvalue.

因此以上對(duì)parameter的轉(zhuǎn)移是不允許的,需要使用std::move來(lái)顯示轉(zhuǎn)換成右值。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多