C++11新標基于C++編程社區的大量實踐經驗,準吸收很多Boost庫的特性,還對原有C++做了一些改進工作,是學習現代C++編程的基礎。這里參考《C++ PRimer Plus 第六版》,對一些常用的C++11新特性做一個總結:
C++11支持對于所有的內置類型和用戶定義類型使用大括號方式的初始化列表,使用初始化列表時,可以添加等號,也可以沒有。
int x = {5}; // 以前只有數組可以這樣初始化double y {2.75};int* ar = new int[4] {2,4,6,8};classType s1(構造參數1, 構造參數2); // 傳統方式創建一個classType對象classType s2{構造參數1, 構造參數2}; // C++11方式創建一個classType對象classType s3 = {構造參數1, 構造參數2}; // C++11方式創建一個classType對象注意:C++11還提供了一個用于構造函數的模板類initializer_list,STL容器的構造函數就使用了這個類
std::vector<int> a1(10); // 聲明包含10個int元素的vector容器std::vector<int> a2={10}; // 聲明容器a2,并初始化一個值為10的元素std::vector<int> a3={4,6,1}; // 聲明容器a3,且初始化三個元素的值為4,6,1傳統意義的左值,指的是出現在賦值語句左邊的表達式。例如:
int a = 20; // 這里的a就是一個左值傳統的C++引用都是使一個標識符關聯到一個左值,所以傳統的C++引用也被稱為左值引用,例如:
int a = 20;int& ra = a; // 即ra是變量a的引用(或別名)C++11新增了右值表達式,采用&&表示。和左值引用(可以關聯到一個左值)類似,右值引用可以關聯到一個右值。具體說:
int a = 10;int b = 20;int c = a + b; // 這里的a+b就是一個右值,以前是沒有辦法對a+b設置引用的int&& r1 = a + b; // 根據C++11,r1可以關聯到a+b產生的那個臨時對象,a、b的值修改了,r1也不變右值包括:字面常量(C風格的字符串除外)、類似于x+y的表達式、帶有返回值的函數(返回值類型不是引用的那種函數)。基本上就是一些編譯器產生的臨時變量,在C++中尤其多。
右值引用的意義:就是為了和傳統左值引用區分開(好像是廢話啊)。要理解右值引用,需要先了解移動語意。 在傳統C++,拷貝構造函數和賦值運算符的重載函數是兩個非常重要的概念。 如果一個類的數據成員中有指針變量,且需要這個類負責這些指針所指向資源的申請和回收。當這個類對象作為函數的參數、返回值或者是一個對象用于給另外一個對象進行初始化時,必須由用戶實現這個類的拷貝構造函數,否則采用編譯器自動生成的拷貝構造函數(淺拷貝),將導致多個對象擁有同一個資源,當銷毀這多個對象時,這些資源面臨重復釋放的問題。所以,在傳統C++的世界里,我們似乎建立了一種觀念即“淺拷貝是不安全的,深拷貝是安全的”。 但是,深拷貝帶來的一個問題就是需要重新申請內存,例如,C++編譯器常用臨時對象初始化一個左值對象。當構造這些臨時對象時,本身就執行了一遍內存申請的操作,再通過這些臨時對象初始化一個左值對象時,左值對象又會因為深拷貝再重新申請內存。很明顯這個過程,對于臨時對象顯得非常的多余,而C++恰好是一種特別喜歡在背后生成大量臨時對象的語言,所以,當臨時對象、深拷貝、STL這些東西遇到一起的時候,經常會導致軟件效率奇低。 C++提出移動語意的原始目的就是要將臨時對象申請的資源通過移動語意直接轉移給左值對象(幾乎就是淺拷貝),并且,接下來臨時對象在析構的時候,也不會再釋放這些已經轉移的內存資源了。
理解了上述關于移動語意的目標了,所以接下來要做的就是,應該采用哪種方式讓編譯器知道什么時候需要轉移,什么時候不需要轉移呢?其實也很簡單,那就是,如果使用傳統的左值引用(T&)就表示不需要轉移,如果使用右值引用(T&&)則表示需要轉移。有了右值引用標識符T&&,程序員通過定義移動構造函數和移動賦值運算符函數就可以實現移動語意的目標了(因為編譯器不提供默認移動構造函數)。具體如下:
class Useless{public: Useless(const Useless& f); // 傳統的拷貝構造函數 Useless(Useless&& f); // 移動構造函數 Useless& Operator=(const Useless& f); // 傳統的賦值運算符重載函數 Useless& operator=(seless&& f); // 移動賦值運算符函數private: int n; char* pc;}// 移動構造函數Useless::Useless(Useless&& f){ n = f.n; pc = f.pc; f.n = 0; f.pc = nullptr;}// 移動賦值運算符函數實現Useless& Useless::operator=(seless&& f){ if (thhis == &f) return *this; delete []pc; n = f.n; pc = f.pc; f.n = 0; f.pc = nullptr; return *this;}強制移動:C++11新增std::move()函數可以無條件將其參數(左值)強制轉換為右值,轉化為右值后,以前由參數控制的資源就可以順利的轉移給另一個左值對象。需要注意的是,move函數執行完后,參數雖然還在,但是參數管理的資源已經轉移了,所以這個參數不能再參與后續的計算了,否則一般都會出現異常。
總結:對于大多數程序員而言,右值引用帶來的最大好處并非是自己編寫使用右值引用的代碼。而是很多STL利用右值引用實現了移動語意,程序員在使用這些STL時,代碼性能會更好。
參考資料:關于右值引用、移動語意、完美轉發
智能指針是支撐RAII(獲取資源即初始化)編程理念的重要方法。對于C++的RAII,簡單的講,就是所有在堆上分配的資源都委托一個棧對象管理。通過棧對象的自動釋放,實現對上資源的自動釋放,有效防止內存泄露。 C++11中的智能指針包括三種:std::unique_ptr、std::shared_ptr和std::weak_ptr。 這三種智能指針都是模板類,每個實例化的智能指針對象都是一個用于管理裸指針的棧對象。所以,智能指針對象一般都是直接定義在函數的棧上(不會使用new運算符創建),智能指針對象的內部實現一定是管理一個原始指針,且這個原始指針最終要指向堆上分配的資源。 因為智能指針重載指針運算符(-> 和 *)以返回封裝的原始指針,所以,程序員可以像使用普通指針一樣使用智能指針。 std::unique_ptr智能指針的創建和使用:
class Test{public: Test(){} Test(int x, int y) : x_(x),y_(y) {} void output() { std::cout << "x=" << x_ << " y=" << y_ << std::endl; }private: int x_; int y_;};int main(){ // C++11中一般只能用下面兩種方式創建unique_ptr std::unique_ptr<Test> pT4 = std::unique_ptr<Test>(new Test(4,4)); std::unique_ptr<Test> pT4(new Test(4,4)); std::unique_ptr<Test[]> pT3(new Test[4]); // 創建被智能指針管理的對象數組 // unique_ptr獨占指針對象,并保證指針所指對象生命周期與其一致。 // unique_ptr對象禁止復制,無法通過值傳遞到函數(可以傳遞引用), // 也無法用于需要副本的任何標準模板庫(STL)算法。 std::unique_ptr<Test> pT5 = pT4; // 這一行會產生編譯錯誤 std::unique_ptr<Test> pT6; // 允許創建一個空對象 pT4->output(); // 像使用普通指針一樣,這一行可以正常運行 pT6->output(); // pT6還是一個空對象,所以這一行會產生異常 pT6= std::move(pT4); // pT4管理的指針指向的資源被強制轉移到pT6 pT6->output(); // 此時pT6已不是空對象,所以這一行可以正常運行 pT4->output(); // 此時pT4變成了空對象,所以這一行會產生異常 if (pT4) // 可以使用if語句判斷一個空對象,就像判斷普通指針一樣 pT4->output(); // 函數的返回值可以是unique_ptr,可用于初始化另一個unique_ptr對象 std::unique_ptr<Test> pT7 = clone(7); pT7->output(); std::vector<std::unique_ptr<Test>> vec; // 創建一個保存智能指針對象的容器, vec.push_back(std::move(pT7)); // 使用移動語義將pT7的資源轉移到STL容器中 pT6.reset(); // 解除pT6關聯的原始指針,則pT6變為空對象}std::unique_ptr<Test> clone(int p){ std::unique_ptr<Test> pTest(new Test(p, p)); return pTest; // 返回unique_ptr}std::unique_ptr的成員函數:
| 函數名 | 作用 |
|---|---|
| release | 返回一個指向被管理對象的指針,并釋放所有權 |
| reset | 替換所管理的對象 |
| swap | 交換所管理的對象 |
| get | 返回指向被管理對象的指針 |
| get_deleter | 返回刪除器,用于被管理對象的析構 |
| operator bool | 檢查是否有關聯的被管理對象 |
| operator* | 對指向被管理對象的指針進行解引用 |
| operator-> | 對指向被管理對象的指針進行解引用 |
| operator[] | 提供對所管理數組的按索引訪問 |
總結std::unique_ptr的特點: 1、堆資源的管理者始終只有一個,一般不會導致堆資源的生存周期被意外的延長。 2、函數內部創建堆資源,并將堆資源轉移到函數外部使用和管理,且不會產生堆資源所有權混亂的問題。 3、一般情況下使用std::unique_ptr就足夠支持堆資源自動管理了,如果有多線程共同使用堆資源,或者是智能指針對象既要保存到STL容器中,同時還要在容器外部使用,此時就必須考慮使用std::shared_ptr(注意不要引起循環引用)。
std::shared_ptr智能指針的創建和使用: 此鏈接詳細分析了shared_ptr和weak_ptr的實現原理
int main(){ // C+11允許用下面三種方式創建shared_ptr std::shared_ptr<Test>sp1(std::make_shared<Test>(1,1)); std::shared_ptr<Test>sp2 = std::make_shared<Test>(2,2); std::shared_ptr<Test>sp3(new Test(3,3)); // shared_ptr允許復制,此時shared_ref_count=2 std::shared_ptr<Test>sp4 = sp1; // weak_ptr總是通過shared_ptr復制,但是不會導致的shared_ref_count增加 std::weak_ptr<Test>wp5 = sp4; std::shared_ptr<Test>sp6 = wp5.lock();// 使用weak_ptr之前必須先通過lock觀察 if (sp6) { sp6->output(); }}把一個 shared_ptr對象傳遞給另一個函數的幾種方式: 1、通過值傳遞shared_ptr,將會調用智能指針的復制構造函數生成一個屬于被調用方的shared_ptr,此時智能指針的引用計數會增加,被調用方也成為了一個所有者。這次操作中有少量的開銷,很大程度上取決于傳遞了多少 shared_ptr 對象。當調用方和被調用方之間的代碼協定 (隱式或顯式) 要求被調用方是所有者,使用此選項。 2、通過引用或常量引用來傳遞 shared_ptr。在這種情況下,引用計數不增加,并且只要調用方不超出范圍(正常情況下,調用函數和被調用函數都在一個線程,肯定不會超出范圍),被調用方就可以訪問指針。或者,被調用方可以決定創建一個基于引用的 shared_ptr,從而成為一個共享所有者。當調用者并不知道被被調用方,或當您必須傳遞一個 shared_ptr,并希望避免由于性能原因的復制操作,請使用此選項。 3、獲取被 shared_ptr管理指針,并向函數傳遞這個原始指針。這使得被調用方可以根據指針使用對象,但原來的 shared_ptr中的引用計數不會增加或對象的生存期也不會擴展生。如果被調用方用接收到的原始指針創建一個shared_ptr,則新的 shared_ptr 是獨立于原來的(一般都不應該這么干)。
以前C++中,一些短小的函數,可以寫成內聯函數,到了C++11中,則可以使用Lambda函數來表示。 所以,lambda函數仍然是一個可調用的代碼單元,可以將其理解為一個未命名的內聯函數,C++11中Lambda表達式具體形式如下: [capture](parameters)->return-type{body} 如果沒有參數,空的圓括號()可以省略,如果函數沒有返回值得話,“->return-type”也可以省略。
Lambda函數可以引用在它之外聲明的變量. 這些變量的集合叫做一個閉包. 閉包被定義在Lambda表達式聲明中的方括號[]內. 這個機制允許這些變量被按值或按引用捕獲。
[] //未定義變量.試圖在Lambda內使用任何外部變量都是錯誤的.[x, &y] //x 按值捕獲, y 按引用捕獲.[&] //用到的任何外部變量都隱式按引用捕獲[=] //用到的任何外部變量都隱式按值捕獲[&, x] //x顯式地按值捕獲. 其它變量按引用捕獲[=, &z] //z按引用捕獲. 其它變量按值捕獲從使用上看,Lambda函數的作用和函數指針或函數對象相似。具體的使用方式如下:
int main(){ std::vector<int> number={1,2,3,4,5,6,7,8,9,10}; int count = 0; std::for_each(number.begin(), number.end(), [&count](int x){count = count+x;}); printf("count = %d /n", count); // 也可以給Lambda函數定義一個名字,這樣可以通過名字調用Lambda函數 auto func = [] () { printf("Hello world"); }; func(); }如上所示,Lambda函數的好處就是函數的定義和函數的使用在一起,所以,一般要求Lambda函數必須很短。
如果理解函數指針、函數對象、(具名)Lambda函數。我們會突然發現,C++中有多種類似函數的調用形式,這些調用形式的存在對于普通程序原來講其實無所謂,夠用就行。但是對于模板化編程來說,太多的調用形式會導致使用了這些調用形式的模板被實例化多次。所以,C++11提出了將多種函數調用形式統一的包裝器std::function。
所以,簡單的說,std::function是對C++中各種可調用實體(普通函數、Lambda表達式、函數指針、以及其它函數對象等)的一個統一封裝,形成一個新的統一可調用對象(std::function對象)(主要是對模板化編程很有意義)。
#include <functional>#include <iostream>using namespace std;std::function< int(int)> Functional;// 普通函數int TestFunc(int a){ return a;}// Lambda表達式auto lambda = [](int a)->int{ return a; };// 仿函數(functor)class Functor{public: int operator()(int a) { return a; }};// 1.類成員函數// 2.類靜態函數class TestClass{public: int ClassMember(int a) { return a; } static int StaticMember(int a) { return a; }};int main(){ // 普通函數 Functional = TestFunc; int result = Functional(10); cout << "普通函數:"<< result << endl; // Lambda表達式 Functional = lambda; result = Functional(20); cout << "Lambda表達式:"<< result << endl; // 仿函數 Functor testFunctor; Functional = testFunctor; result = Functional(30); cout << "仿函數:"<< result << endl; // 類成員函數 TestClass testObj; Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1); result = Functional(40); cout << "類成員函數:"<< result << endl; // 類靜態函數 Functional = TestClass::StaticMember; result = Functional(50); cout << "類靜態函數:"<< result << endl; return 0;}std::bind機制可以預先把已有的變量和可調用實體的某些參數綁定,產生一個新的可調用實體,這種機制在回調函數的使用過程中也頗為有用。以前類的成員函數因為參數中隱含this指針,所以無法直接注冊為回調函數,現在可以通過bind提前綁定this指針注冊為回調函數bind的返回值是可調用實體,可以直接賦給std::function對象。
auto newConnectionCallback_ = std::bind(&EchoServer::newConnection, this, std::placeholders::_1);bind能夠在綁定時候就同時綁定一部分參數,未提供的參數則使用占位符表示,然后在運行時傳入實際的參數值。如上所示,EchoServer::newConnection表示被包裝的函數,第一個參數表示綁定函數所屬對象的this指針。std::placeholders::_1表示是新函數的第1個參數(依次類推)。
bind的返回值是可調用實體,可以直接賦給std::function對象。
新聞熱點
疑難解答
圖片精選