C++11 中值得關注的幾大變化

C++11 中值得關注的幾大變化

首先呢,小編覺得c++17雖然已經出來了,c++20也即將面世,但是並不代表著C++11 的沒落,我們現在所學到的C++知識大多還是C++11 的,並不是書本不更新,而是其仍然起著重要的作用!接下來我們就來看看C++11中的一些值得關注的幾大變化吧!

Lambda 表示式

Lambda表示式來源於函數語言程式設計,說白就了就是在使用的地方定義函式,有的語言叫“閉包”,如果 lambda 函式沒有傳回值(例如 void ),其回返型別可被完全忽略。 定義在與 lambda 函式相同作用域的變數參考也可以被使用。這種的變數集合一般被稱作 closure(閉包)。我在這裡就不再講這個事了。表示式的簡單語法如下,

[capture](parameters)->return_type {body}

原文的作者給出了下面的例子:

int

main()

{

char

s[]=“Hello World!”;

int

Uppercase = 0;

//modified by the lambda

for_each(s, s+

sizeof

(s), [&Uppercase] (

char

c) {

if

(isupper(c))

Uppercase++;

});

cout

<< Uppercase << “ uppercase letters in: ” << s <

}

在傳統的STL中for_each() 這個玩意最後那個引數需要一個“函式物件”,所謂函式物件,其實是一個class,這個class過載了operator(),於是這個物件可以像函式的式樣的使用。實現一個函式物件並不容易,需要使用template,比如下面這個例子就是函式物件的簡單例子(實際的實現遠比這個複雜):

template

<

class

T>

class

less

{

public:

bool

operator

()(const T&l, const T&r)const

{

return

l < r;

}

};

C++引入Lambda的最主要原因就是:1)可以定義匿名函式,2)編譯器會把其轉成函式物件

那麼,除了方便外,為什麼一定要使用Lambda呢?它比傳統的函式或是函式物件有什麼好處呢?我個人所理解的是,這種函式之年以叫“閉包”,就是因為其限制了別人的訪問,更私有。也可以認為他是一次性的方法。Lambda表示式應該是簡潔的,極私有的,為了更易的程式碼和更方便的程式設計。

自動型別推導 auto

在這一節中,原文主要介紹了兩個關鍵字 auto 和 deltype,示例如下:

1。

auto x=0;

//x has type int because 0 is int

auto c=‘a’;

//char

auto d=0。5;

//double

auto national_debt=14400000000000LL;

//long long

2。

vector<

int

>::const_iterator ci = vi。begin();

可以變成:

auto ci = vi。begin();

模板這個特性讓C++的程式碼變得很難讀,不信你可以看看STL的原始碼,那是一個亂啊。使用auto必需一個初始化值,編譯器可以透過這個初始化值推匯出型別。因為auto是來簡化模板類引入的程式碼難讀的問題,如上面的示例,iteration這種型別就最適合用auto的,但是,我們不應該把其濫用。

比如下面的程式碼的可讀性就降低了。因為,我不知道ProcessData返回什麼?int? bool? 還是物件?或是別的什麼?這讓你後面的程式不知道怎麼做。

auto obj = ProcessData(someVariables);

但是下面的程式就沒有問題,因為pObject的型別在後面的new中有了。

auto pObject =

new

SomeType::SomeOtherType();

自動化推導 decltype

關於 decltype 是一個運算子,其可以評估括號內表示式的型別,其規則如下:

如果表示式e是一個變數,那麼就是這個變數的型別。

如果表示式e是一個函式,那麼就是這個函式返回值的型別。

如果不符合1和2,如果e是左值,型別為T,那麼decltype(e)是T&;如果是右值,則是T。

原文給出的示例如下,我們可以看到,這個讓的確我們的定義變數省了很多事。

const vector<

int

> vi;

typedef

decltype

(vi。begin()) CIT;

CIT another_const_iterator;

還有一個適合的用法是用來typedef函式指標,也會省很多事。比如:

decltype

(&myfunc) pfunc = 0;

typedef

decltype

(&A::func1) type;

auto 和 decltype 的差別和關係

Wikipedia 上是這麼說的(關於decltype的規則見上)

#include

int

main()

{

const std::vector<

int

> v(1);

auto a = v[0];

// a 的型別是 int

decltype

(v[0]) b = 1;

// b 的型別是 const int&, 因為函式的返回型別是

// std::vector::operator[](size_type) const

auto c = 0;

// c 的型別是 int

auto d = c;

// d 的型別是 int

decltype

(c) e;

// e 的型別是 int, 因為 c 的型別是int

decltype

((c)) f = c;

// f 的型別是 int&, 因為 (c) 是左值

decltype

(0) g;

// g 的型別是 int, 因為 0 是右值

}

如果auto 和 decltype 在一起使用會是什麼樣子?能看下面的示例,下面這個示例也是引入decltype的一個原因——讓C++有能力寫一個模板”

template

<

typename

LHS,

typename

RHS>

auto AddingFunc(const LHS &lhs, const RHS &rhs) ->

decltype

(lhs+rhs)

{

return

lhs + rhs;}

這個函式模板看起來相當費解,其用到了auto 和 decltype 來擴充套件了已有的模板技術的不足。怎麼個不足呢?在上例中,我不知道AddingFunc會接收什麼樣型別的物件,這兩個物件的 + 運算子返回的型別也不知道,老的模板函式無法定義AddingFunc返回值和這兩個物件相加後的返回值匹配,所以,你可以使用上述的這種定義。

統一的初始化語法

C/C++的初始化的方法比較,C++ 11 用大括號統一了這些初始化的方法。

比如:POD的型別。

int

arr[4]={0,1,2,3};

struct

tm today={0};

關於POD相說兩句,所謂POD就是Plain Old Data,當class/struct是

極簡的(trivial)

、屬於

標準佈局(standard-layout)

,以及他的所有非靜態(non-static)成員都是POD時,會被視為POD。如:

struct

A {

int

m; };

// POD

struct

B { ~B();

int

m; };

// non-POD, compiler generated default ctor

struct

C { C() : m() {}; ~C();

int

m; };

// non-POD, default-initialising m

POD的初始化有點怪,比如上例,new A; 和new A(); 是不一樣的,對於其內部的m,前者沒有被初始化,後者被初始化了(不同 的編譯器行為不一樣,VC++和GCC不一樣)。而非POD的初始化,則都會被初始化。

從這點可以看出,C/C++的初始化問題很奇怪,所以,在C++ 2011版中就做了統一。原文作者給出瞭如下的示例:

C c {0,0};

//C++11 only。 相當於: C c(0,0);

int

* a =

new

int

[3] { 1, 2, 0 }; /C++11 only

class

X {

int

a[4];

public:

X() : a{1,2,3,4} {}

//C++11, member array initializer

};

容器的初始化:

// C++11 container initializer

vector<

string

> vs={ “first”, “second”, “third”};

map singers =

{ {“Lady Gaga”, “+1 (212) 555-7890”},

{“Beyonce Knowles”, “+1 (212) 555-0987”}};

還支援像Java一樣的成員初始化:

class

C

{

int

a=7;

//C++11 only

public:

C();

};

Delete 和 Default 函式

我們知道C++的編譯器在你沒有定義某些成員函式的時候會給你的類自動生成這些函式,比如,建構函式,複製構造,解構函式,賦值函式。有些時候,我們不想要這些函式,比如,建構函式,因為我們想做實現單例模式。傳統的做法是將其宣告成private型別。

在新的C++中引入了兩個指示符,delete意為告訴編譯器不自動產生這個函式,default告訴編譯器產生一個預設的。原文給出了下面兩個例子:

struct

A

{

A()=

default

//C++11

virtual ~A()=

default

//C++11

};

再如delete

struct

NoCopy

{

NoCopy &

operator

=( const NoCopy & ) = delete;

NoCopy ( const NoCopy & ) = delete;

};

NoCopy a;

NoCopy b(a);

//compilation error, copy ctor is deleted

這裡,我想說一下,為什麼我們需要default?我什麼都不寫不就是default嗎?不全然是,比如建構函式,因為只要你定義了一個建構函式,編譯器就不會給你生成一個預設的了。所以,為了要讓預設的和自定義的共存,才引入這個引數,如下例所示:

struct

SomeType

{

SomeType() =

default

// 使用編譯器生成的預設建構函式

SomeType(OtherType

value

);

};

關於delete還有兩個有用的地方是

1)讓你的物件只能生成在棧記憶體上:

struct

NonNewable {

void

*

operator

new

(std::size_t) = delete;

};

2)阻止函式的其形參的型別呼叫:(若嘗試以 double 的形參呼叫 f(),將會引發編譯期錯誤, 編譯器不會自動將 double 形參轉型為 int 再呼叫f(),如果傳入的引數是double,則會出現編譯錯誤)

void

f(

int

i);

void

f(

double

) = delete;

nullptr

C/C++的NULL宏是個被有很多潛在BUG的宏。因為有的庫把其定義成整數0,有的定義成 (void*)0。在C的時代還好。但是在C++的時代,這就會引發很多問題。你可以上網看看。這是為什麼需要 nullptr 的原因。 nullptr 是強型別的。

void

f(

int

);

//#1

void

f(

char

*);

//#2

//C++03

f(0);

//二義性

//C++11

f(

nullptr

//無二義性,呼叫f(char*)

所以在新版中請以 nullptr 初始化指標。

委託構造

在以前的C++中,建構函式之間不能互相呼叫,所以,我們在寫這些相似的構造函數里,我們會把相同的程式碼放到一個私有的成員函式中。

class SomeType {

private:

int number;

string name;

SomeType( int i, string& s ) : number(i), name(s){}

public:

SomeType( ) : SomeType( 0, “invalid” ){}

SomeType( int i ) : SomeType( i, “guest” ){}

SomeType( string& s ) : SomeType( 1, s ){ PostInit(); }

};

但是,為了方便並不足讓“委託構造”這個事出現,最主要的問題是,基類的構造不能直接成為派生類的構造,就算是基類的建構函式夠了,派生類還要自己寫自己的建構函式:

class BaseClass

{

public:

BaseClass(int iValue);

};

class DerivedClass : public BaseClass

{

public:

using BaseClass::BaseClass;

};

上例中,派生類手動繼承基類的建構函式, 編譯器可以使用基類的建構函式完成派生類的構造。 而將基類的建構函式帶入派生類的動作 無法選擇性地部分帶入, 所以,要不就是繼承基類全部的建構函式,要不就是一個都不繼承(不手動帶入)。 此外,若牽涉到多重繼承,從多個基類繼承而來的建構函式不可以有相同的函式簽名(signature)。 而派生類的新加入的建構函式也不可以和繼承而來的基類建構函式有相同的函式簽名,因為這相當於重複宣告。(所謂函式簽名就是函式的引數型別和順序不)

右值引用和move語義

在老版的C++中,臨時性變數(稱為右值”R-values”,位於賦值運算子之右)經常用作交換兩個變數。比如下面的示例中的tmp變數。示例中的那個函式需要傳遞兩個string的引用,但是在交換的過程中產生了物件的構造,記憶體的分配還有物件的複製構造等等動作,成本比較高。

void naiveswap(string &a, string &b)

{

string temp = a;

a=b;

b=temp;

}

C++ 11增加一個新的引用(reference)型別稱作右值引用(R-value reference),標記為typename &&。他們能夠以non-const值的方式傳入,允許物件去改動他們。這項修正允許特定物件創造出move語義。

舉例而言,上面那個例子中,string類中儲存了一個動態記憶體分存的char*指標,如果一個string物件發生複製構造(如:函式返回),string類裡的char*記憶體只能透過建立一個新的臨時物件,並把函式內的物件的記憶體copy到這個新的物件中,然後銷燬臨時物件及其記憶體。

這是原來C++效能上重點被批評的事

能過右值引用,string的建構函式需要改成“move建構函式”,如下所示。這樣一來,使得對某個stirng的右值引用可以單純地從右值複製其內部C-style的指標到新的string,然後留下空的右值。這個操作不需要記憶體陣列的複製,而且空的暫時物件的析構也不會釋放記憶體。其更有效率。

class string

{

string (string&&); //move constructor

string&& operator=(string&&); //move assignment operator

};

The C++11 STL中廣泛地使用了右值引用和move語議。因此,很多演算法和容器的效能都被優化了。

C++ 11 STL 標準庫

C++ STL庫在2003年經歷了很大的整容手術 Library Technical Report 1 (TR1)。 TR1 中出現了很多新的容器類 (unordered_set, unordered_map, unordered_multiset, 和 unordered_multimap) 以及一些新的庫支援諸如:正則表示式, tuples,函式物件包裝,等等。 C++11 批准了 TR1 成為正式的C++標準,還有一些TR1 後新加的一些庫,從而成為了新的C++ 11 STL標準庫。這個庫主要包含下面的功能:

執行緒庫

這們就不多說了,以前的STL飽受執行緒安全的批評。現在好 了。C++ 11 支援執行緒類了。這將涉及兩個部分:第一、設計一個可以使多個執行緒在一個程序中共存的記憶體模型;第二、為執行緒之間的互動提供支援。第二部分將由程式庫提供支援。大家可以看看promises and futures,其用於物件的同步。 async() 函式模板用於發起併發任務,而 thread_local 為執行緒內的資料指定儲存型別。更多的東西,可以檢視 Anthony Williams的 Simpler Multithreading in C++0x。

新型智慧指標

C++98 的知能指標是 auto_ptr, 在C++ 11中被廢棄了。C++11 引入了兩個指標類: shared_ptr 和 unique_ptr。 shared_ptr只是單純的引用計數指標,unique_ptr 是用來取代auto_ptr。 unique_ptr 提供 auto_ptr 大部份特性,唯一的例外是 auto_ptr 的不安全、隱性的左值搬移。不像 auto_ptr,unique_ptr 可以存放在 C++0x 提出的那些能察覺搬移動作的容器之中。

為什麼要這麼幹?大家可以看看《More Effective C++》中對 auto_ptr的討論。

新的演算法

1。定義了一些新的演算法: all_of(), any_of() 和 none_of()。

#include

//C++11 code

//are all of the elements positive?

all_of(first, first+n, ispositive()); //false

//is there at least one positive element?

any_of(first, first+n, ispositive());//true

// are none of the elements positive?

none_of(first, first+n, ispositive()); //false

2。使用新的copy_n()演算法,你可以很方便地複製陣列。

#include

int source[5]={0,12,34,50,80};

int target[5];

//copy 5 elements from source to target

copy_n(source,5,target);

3。可以用來建立遞增的數列。如下例所示:

include

int a[5]={0};

char c[3]={0};

iota(a, a+5, 10); //changes a to {10,11,12,13,14}

iota(c, c+3, ‘a’); //{‘a’,‘b’,‘c’}

總之,看下來,C++11 還是很學院派,很多實用的東西還是沒有,比如: XML,sockets,reflection,當然還有垃圾回收。看來要等到C++ 20了。呵呵。不過C++ 11在效能上還是很快。原文還引用Stroustrup 的觀點:C++11 是一門新的語言——一個更好的 C++。

如果把所有的改變都列出來,你會發現真多啊。我估計C++ Primer那本書的厚度要增加至少30%以上。C++的門檻會不會越來越高了呢?我不知道,但我個人覺得這門語言的確是變得越來越令人望而卻步了。

注:喜歡小編文章可以關注收藏!有興趣學習C/C++的小夥伴可以進群:941636044