天天看點

常識普及-C++ 開發效率的幾個小案例

我們說的 Modern C++,一般指的是 C++11 及以後的标準,從 C++ 11 開始,Modern C++ 引入了大量的實用的特性,主要是兩大方面,學習的時候也可以從這兩大方面學習:

  1. 增強或者改善的文法特性;
  2. 新增的或者改善的 STL 庫。

我們來看幾個具體的案例:

案例 1:統一的類成員初始化文法與 std::initializer_list:

在 C++98/03 中,假設我們要初始化一個類數組類型的成員(例如常用的清零操作),我們需要這麼寫:

class A
 {
 public:
     A()
     {
         //初始化arr
         arr[0] = 0;
         arr[1] = 0;
         arr[2] = 0;
         arr[3] = 0;
     }
 
 public:
     int arr[4];
 };      

假設數組 arr 較長,我們可以使用循環或者借助 memset 函數去初始化,代碼如下:

class A
 {
 public:
     A()
     {
         //使用循環初始化arr
         for (int i = 0; i < 4; i++)
             arr[i] = 0;
     }
 
 public:
     int arr[4];
 };
 
 class A
 {
 public:
     A()
     {
         //使用memset初始化arr
         memset(arr, 0, sizeof(arr));
     }
 
 public:
     int arr[4];
 };      

但是,我們知道,在 C++98/08 中我們可以直接通過指派操作來初始化一個數組的:

int arr[4] = { 0 };      

但是對于作為類的成員變量的數組元素,C++98/03 是不允許我們這麼做的。

到 C++11 中全部放開并統一了,在 C++11 中我們也可以使用這樣的文法是初始化數組:

class A
 {
 public:
     //在 C++11中可以使用大括号文法初始化數組類型的成員變量
     A() : arr{0}
     {
     }
 
 public:
     int arr[4];
 };      

如果你有興趣,我們可以更進一步:

在 C++ 98/03 标準中,對類的成員必須使用 static const 修飾,而且類型必須是整型 (包括 bool、 char、 int、 long 等),這樣才能使用這種初始化文法:

//C++98/03 在類定義處初始化成員變量
 class A
 {
 public:
     //T 的類型必須是整型,且必須使用 static const 修飾
     static const T t = 某個整型值;
 };      

在 C++11 标準中就沒有這種限制了,我們可以使用花括号(即{})對任意類型的變 量進行初始化,而且不用是 static 類型:

//C++ 11 在類定義處初始化成員變量
 class A
 {
 public:
     //有沒有一種Java初始化類成員變量的即視感^ _ ^
     bool ma{true};
     int mb{2019};
     std::string mc{"helloworld"};
 };      

當然,在實際開發中,建議還是将這些成員變量的初始化統一寫到構造函數的初始化清單中,友善閱讀和維護代碼。

案例 2:注解标簽

C++ 14 引入了 [[deprecated]] 标簽來表示一個函數或者類型等已被棄用,在使用這些被棄用的函數或者類型并編譯時, 編譯器會給出相應的警告, 有的編譯器直接生成編譯錯誤:

[[deprecated]] void funcX();      

這個标簽在實際開發中非常有用,尤其在設計一些庫代碼時,如果庫作者希望某個函數或者類型不想再被使用者使用,則可以使用該标注标記。當然,我們也可以使用如下文法給出編譯時的具體警告或者出錯資訊:

[[deprecated("use funY instead")]] void funcX();      

有如下代碼:

#include <iostream>
 [[deprecated("use funcY instead")]] void funcX()
 {
     //實作省略
 }
 
 int main()
 {
     funcX();
     return 0;
 }      

若在 main 函數中調用被标記為 deprecated 的函數 funcX,則在 gcc/g++7.3 中編譯時會得到如下警告資訊:

[root@myaliyun testmybook]# g++ -g -o test_attributes test_attributes.cpp
 test_attributes.cpp: In function ‘int main()’:
 test_attributes.cpp:10:11: warning: ‘void funcX()’ is deprecated: use funcY instead
 [-Wdeprecated-declarations]
 funcX();
 ^
 test_attributes.cpp:3:42: note: declared here
 [[deprecated("use funcY instead")]] void funcX()      
Java 開發者對這個标注應該再熟悉不過了。在 Java 中使用@Deprecated 标注可以達到同樣的效果,這大概是 C++标準委員“拖欠”廣大 C++開發者太久的一個特性吧。

C++ 17 提供了三個實用注解:[[fallthrough]]、 [[nodiscard]] 和 [[maybe_unused]],這裡 逐一介紹它們的用法。

[[fallthrough]] 用于 switch-case 語句中,在某個 case 分支執行完畢後如果沒有 break 語句,則編譯器可能會給出一條警告。但有時這可能是開發者有意為之的。為了讓編譯器明确知道開發者的意圖,可以在需要某個 case 分支被“貫穿”的地方(上一個 case 沒有break 語句)顯式設定 [[fallthrough]] 标記。代碼示例如下:

switch (type)
 {
 case 1:
     func1();
     //這個位置缺少 break 語句,且沒有 fallthrough 标注,
     //可能是一個邏輯錯誤,在編譯時編譯器可能會給出警告,以提醒修改
 
 case 2:
     func2();
     //這裡也缺少 break 語句,但是使用了 fallthrough 标注,
     //說明是開發者有意為之的,編譯器不會給出任何警告
     [[fallthrough]];
 
 case 3:
     func3();
 }      
注意:在 gcc/g++中, [[fallthrough]] 後面的分号不是必需的,在 Visual Studio 中必須加上分号,否則無法編譯通過。

熟悉 Golang 的讀者,可能對 fallthrough 這一文法特性非常熟悉, Golang 中在 switch-case 後加上 fallthrough,是一個常用的告訴編譯器意圖的文法規則。代碼示例如下:

//以下是 Golang 文法
 s := "abcd"
 switch s[3] {
     case 'a':
         fmt.Println("The integer was <= 4")
         fallthrough
 
     case 'b':
         fmt.Println("The integer was <= 5")
         fallthrough
 
     case 'c':
         fmt.Println("The integer was <= 6")
 
     default:
         fmt.Println("default case")
 }      

[[nodiscard]] 一般用于修飾函數,告訴函數調用者必須關注該函數的傳回值(即不能丢棄該函數的傳回值)。如果函數調用者未将該函數的傳回值指派給一個變量,則編譯器會給出一個警告。例如,假設有一個網絡連接配接函數 connect,我們通過傳回值明确說明了連接配接是否建立成功,則為了防止調用者在使用時直接将該值丢棄,我們可以将該函數使用 [[nodiscard]] 标記:

[[nodiscard]] int connect(const char* address, short port)
 {
     //實作省略
 }
 
 int main()
 {
     //忽略了connect函數的傳回值,編譯器會給出一個警告
     connect("127.0.0.1", 8888);
     return 0;
 }      

在 C++ 20 中,對于諸如 operator new()、 std::allocate()等庫函數均使用了 [[nodiscard]] 進行标記,以強調必須使用這些函數的傳回值。

再來看另外一個标記。

在通常情況下,編譯器會對程式代碼中未使用的函數或變量給出警告,另一些編譯器幹脆不允許通過編譯。在 C++ 17 之前,程式員為了消除這些未使用的變量帶來的編譯警告或者錯誤,要麼修改編譯器的警告選項設定,要麼定義一個類似于 UNREFERENCED_PARAMETER 的宏來顯式調用這些未使用的變量一次,以消除編譯警告或錯誤:

#define UNREFERENCED_PARAMETER(x) x
 
 int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
 {
     //C++17之前為了消除編譯器對未使用的變量hPrevInstance、lpCmdLine給出的警告,我們可以這麼做
     UNREFERENCED_PARAMETER(hPrevInstance);
     UNREFERENCED_PARAMETER(lpCmdLine);
     //無關代碼省略
 }      

以上代碼節選自一個标準 Win32 程式的結構,其中的函數參數 hPrevInstance 和 lpCmdLine 一般不會被用到,編譯器會給出警告。為了消除這類警告,這裡定義了一個宏 UNREFERENCED_PARAMETER 并進行調用,造成這兩個參數被使用的假象。

C++17 有了 [[maybe_unused]] 注解之後,我們就再也不需要這類宏來“欺騙”編譯器了。以上代碼使用該注解後可以修改如下:

int APIENTRY wWinMain(HINSTANCE hInstance,
                       [[maybe_unused]] HINSTANCE hPrevInstance,
                       [[maybe_unused]] LPWSTR lpCmdLine,
                       int nCmdShow)
 {
     //無關代碼省略
 }      

案例 3:final、 override 關鍵字和 =default、 =delete 文法

3.1 final 關鍵字

在 C++11 之前,我們沒有特别好的方法阻止一個類被其他類繼承,到了 C++11 有了 final 關鍵字我們就可以做到了。final 關鍵字修飾一個類,這個類将不允許被繼承,這在其他語言(如 Java)中早就實作了。在 C++ 11 中, final 關鍵字要寫在類名的後面,這在其他語言中是寫在 class 關鍵字前面的。示例如下:

class A final
 {
 };
 
 class B : A
 {
 };      

由于類 A 被聲明成 final, B 繼承 A, 是以編譯器會報如下錯誤提示類 A 不能被繼承:

error C3246: 'B' : cannot inherit from 'A' as it has been declared as 'final'      

3.2 override 關鍵字

C++98/03 文法規定,在父類中加了 virtual 關鍵字的方法可以被子類重寫,子類重寫該方法時可以加或不加 virtual 關鍵字,例如下面這樣:

class A
 {
 protected:
     virtual void func(int a, int b)
     {
     }
 };
 
 class B : A
 {
 protected:
     virtual void func(int a, int b)
     {
     }
 };
 
 class C : B
 {
 protected:
     void func(int a, int b)
     {
     }
 };      

這種寬松的規定可能會帶來以下兩個問題。

  • 當我們閱讀代碼時,無論子類重寫的方法是否添加了 virtual 關鍵字,我們都無法 直覺地确定該方法是否是重寫的父類方法。
  • 如果我們在子類中不小心寫錯了需要重寫的方法的函數簽名(可能是參數類型、 個數或傳回值類型),這個方法就會變成一個獨立的方法,這可能會違背我們重寫 父類某個方法的初衷,而編譯器在編譯時并不會檢查到這個錯誤。

為了解決以上兩個問題, C++11 引進了 override 關鍵字,其實 override 關鍵字并不是新文法,在 Java 等其他程式設計語言中早就支援。類方法被 override 關鍵字修飾,表明該方法重寫了父類的同名方法,加了該關鍵字後,編譯器會在編譯階段做相應的檢查,如果其父類不存在相同簽名格式的類方法,編譯器就會給出相應的錯誤提示。

情形一,父類不存在,子類标記了 override 的方法:

class A
 {
 };
 
 class B : A
 {
 protected:
     void func(int k, int d) override
     {
     }
 };      

由于在父類 A 中沒有 func 方法,是以編譯器會提示錯誤:

error C3668: 'B::func' : method with override specifier 'override' did not override
 any base class methods      

情形二,父類存在,子類标記了 override 的方法,但函數簽名不一緻:

class A
 {
 protected:
     virtual int func(int k, int d)
     {
         return 0;
     }
 };
 
 class B : A
 {
 protected:
     virtual void func(int k, int d) override
     {
     }
 };      

上述代碼編譯器會報同樣的錯誤。正确的代碼如下:

class A
 {
 protected:
     virtual void func(int k, int d)
     {
     }
 };
 
 class B : A
 {
 protected:
     virtual void func(int k, int d) override
     {
     }
 };      

3.3 default 文法

如果一個 C++類沒有顯式給出構造函數、析構函數、拷貝構造函數、 operator= 這幾類函數的實作,則在需要它們時,編譯器會自動生成;或者,在給出這些函數的聲明時,如果沒有給出其實作,則編譯器在連結時會報錯。如果使用=default 标記這類函數,則編譯器會給出預設的實作。來看一個例子:

class A
 {
 };
 
 int main()
 {
     A a;
     return 0;
 }      

這樣的代碼是可以編譯通過的,因為編譯器預設生成 A 的一個無參構造函數,假設我們現在向 A 提供一個有參構造函數:

class A
 {
 public:
     A(int i)
     {
     }
 };
 
 int main()
 {
     A a;
     return 0;
 }      

這時,編譯器就不會自動生成預設的無參構造函數了,這段代碼會編譯出錯,提示 A 沒有合适的無參構造函數:

error C2512: 'A' : no appropriate default constructor available      

我們這時可以手動為 A 加上無參構造函數, 也可以使用=default 文法強行讓編譯器自己生成:

class A
 {
 public:
     A() = default;
     A(int i)
     {
     }
 };
 
 int main()
 {
     A a;
     return 0;
 }      

=default 最大的作用可能是在開發中簡化了構造函數中沒有實際初始化代碼的寫法,尤其是聲明和實作分别屬于.h 和.cpp 檔案。例如,對于類 A,其頭檔案為 a.h,其實作檔案為 a.cpp,則正常情況下我們需要在 a.cpp 檔案中寫其構造函數和析構函數的實作(可能沒有實際的構造和析構邏輯):

//a.h
 class A
 {
 public:
     A();
     ~A();
 };
 
 //a.cpp
 #include "a.h"
 
 A::A()
 {
 }
 
 A::~A()
 {
 }      

可以發現,即使在 A 的構造函數和析構函數中什麼邏輯也沒有,我們還是不得不在 a.cpp 中寫上構造函數和析構函數的實作。有了=default 關鍵字,我們就可以在 a.h 中直接寫成:

//a.h
 class A
 {
 public:
     A() = default;
     ~A() = default;
 };
 
 //a.cpp
 #include "a.h"
 //在 cpp 檔案中就不用再寫 A 的構造函數和析構函數的實作了      

3.4 =delete 文法

既然有強制讓編譯器生成構造函數、析構函數、拷貝構造函數、 operator=的文法,那麼也應該有禁止編譯器生成這些函數的文法,沒錯,就是 =delete。在 C++ 98/03 規範中, 如果我們想讓一個類不能被拷貝(即不能調用其拷貝構造函數),則可以将其拷貝構造函數和 operator=函數定義成 private 的:

class A
 {
 public:
     A() = default;
     ~A() = default;
 
 private:
     A(const A& a)
     {
     }
     
     A& operator =(const A& a)
     {
     }
 };
 
 int main()
 {
     A a1;
     A a2(a1);
     A a3;
     a3 = a1;
     return 0;
 }      

通過以上代碼利用 a1 構造 a2 時,編譯器會提示錯誤:

error C2248: 'A::A' : cannot access private member declared in class 'A'
 error C2248: 'A::operator =' : cannot access private member declared in class 'A'      

我們利用這種方式間接實作了一個類不能被拷貝的功能,這也是繼承自 boost::noncopyable 的類不能被拷貝的實作原理。現在有了=delete文法,我們直接使用該文法禁止編譯器生成這兩個函數即可:

class A
 {
 public:
     A() = default;
     ~A() = default;
 public:
     A(const A& a) = delete;
     A& operator =(const A& a) = delete;
 };
 
 int main()
 {
     A a1;
     //A a2(a1);
     A a3;
     //a3 = a1;
     return 0;
 }      

一般在一些工具類中, 我們不需要用到構造函數、 析構函數、 拷貝構造函數、 operator= 這 4 個函數,為了防止編譯器自己生成,同時為了減小生成的可執行檔案的體積,建議使用=delete 文法禁止編譯器為這 4 個函數生成預設的實作代碼,例如:

//這是一個字元轉碼工具類
 class EncodeUtil
 {
 public:
     static std::wstring AnsiiToUnicode(const std::string& strAnsii);
     static std::string UnicodeToAnsii(const std::wstring& strUnicode);
     static std::string AnsiiToUtf8(const std::string& strAnsii);
     static std::string Utf8ToAnsii(const std::string& strUtf8);
     static std::string UnicodeToUtf8(const std::wstring& strUnicode);
     static std::wstring Utf8ToUnicode(const std::string& strUtf8);
     
 private:
     EncodeUtil() = delete;
     ~EncodeUtil() = delete;
     EncodeUtil(const EncodeUtil& rhs) = delete;
     EncodeUtil& operator=(const EncodeUtil& rhs) = delete;
 };      

案例 4:對多線程的支援

我們來看一個稍微複雜一點的例子。

在 C++11 之前,由于 C++98/03 本身缺乏對線程和線程同步原語的支援,我們要寫一個生産者消費者邏輯要這麼寫。

在 Windows 上:

/**
  * RecvMsgTask.h
  */
 class CRecvMsgTask : public CThreadPoolTask
 {
 public:
     CRecvMsgTask(void);
     ~CRecvMsgTask(void);
 
 public:
     virtual int Run();
     virtual int Stop();
     virtual void TaskFinish();
 
     BOOL AddMsgData(CBuffer* lpMsgData);
 
 private:
     BOOL HandleMsg(CBuffer* lpMsg);
 
 private:
     HANDLE                m_hEvent;
     CRITICAL_SECTION      m_csItem;
     HANDLE                m_hSemaphore;
     std::vector<CBuffer*> m_arrItem;
 };
 
 /**
  * RecvMsgTask.cpp
  */
 CRecvMsgTask::CRecvMsgTask(void)
 {
     ::InitializeCriticalSection(&m_csItem);
     m_hSemaphore = ::CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
     m_hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);
 }
 
 CRecvMsgTask::~CRecvMsgTask(void)
 {
     ::DeleteCriticalSection(&m_csItem);
 
     if (m_hSemaphore != NULL)
     {
         ::CloseHandle(m_hSemaphore);
         m_hSemaphore = NULL;
     }
 
     if (m_hEvent != NULL)
     {
         ::CloseHandle(m_hEvent);
         m_hEvent = NULL;
     }
 }
 
 int CRecvMsgTask::Run()
 {
     HANDLE hWaitEvent[2];
     DWORD dwIndex;
     CBuffer * lpMsg;
 
     hWaitEvent[0] = m_hEvent;
     hWaitEvent[1] = m_hSemaphore;
 
     while (1)
     {
         dwIndex = ::WaitForMultipleObjects(2, hWaitEvent, FALSE, INFINITE);
 
         if (dwIndex == WAIT_OBJECT_0)
             break;
 
         lpMsg = NULL;
 
         ::EnterCriticalSection(&m_csItem);
         if (m_arrItem.size() > 0)
         {
             //消費者從隊列m_arrItem中取出任務執行
             lpMsg = m_arrItem[0];
             m_arrItem.erase(m_arrItem.begin() + 0);
         }
         ::LeaveCriticalSection(&m_csItem);
 
         if (NULL == lpMsg)
             continue;
 
         //處理任務
         HandleMsg(lpMsg);
 
         delete lpMsg;
     }
 
     return 0;
 }
 
 int CRecvMsgTask::Stop()
 {
     m_HttpClient.SetCancalEvent();
     ::SetEvent(m_hEvent);
     return 0;
 }
 
 void CRecvMsgTask::TaskFinish()
 {
 }
 
 //生産者調用這個方法将Task放入隊列m_arrItem中
 BOOL CRecvMsgTask::AddMsgData(CBuffer * lpMsgData)
 {
     if (NULL == lpMsgData)
         return FALSE;
 
     ::EnterCriticalSection(&m_csItem);
     m_arrItem.push_back(lpMsgData);
     ::LeaveCriticalSection(&m_csItem);
 
     ::ReleaseSemaphore(m_hSemaphore, 1, NULL);
 
     return TRUE;
 }      

在 Linux 下:

#include <pthread.h>
 #include <errno.h>
 #include <unistd.h>
 #include <list>
 #include <semaphore.h>
 #include <iostream>
 
 class Task
 {
 public:
     Task(int taskID)
     {
         this->taskID = taskID;
     }
     
     void doTask()
     {
         std::cout << "handle a task, taskID: " << taskID << ", threadID: " << pthread_self() << std::endl; 
     }
     
 private:
     int taskID;
 };
 
 pthread_mutex_t  mymutex;
 std::list<Task*> tasks;
 pthread_cond_t   mycv;
 
 void* consumer_thread(void* param)
 {   
     Task* pTask = NULL;
     while (true)
     {
         pthread_mutex_lock(&mymutex);
         while (tasks.empty())
         {               
             //如果獲得了互斥鎖,但是條件不合适的話,pthread_cond_wait會釋放鎖,不往下執行。
             //當發生變化後,條件合适,pthread_cond_wait将直接獲得鎖。
             pthread_cond_wait(&mycv, &mymutex);
         }
         
         pTask = tasks.front();
         tasks.pop_front();
 
         pthread_mutex_unlock(&mymutex);
         
         if (pTask == NULL)
             continue;
 
         pTask->doTask();
         delete pTask;
         pTask = NULL;       
     }
     
     return NULL;
 }
 
 void* producer_thread(void* param)
 {
     int taskID = 0;
     Task* pTask = NULL;
     
     while (true)
     {
         pTask = new Task(taskID);
             
         pthread_mutex_lock(&mymutex);
         tasks.push_back(pTask);
         std::cout << "produce a task, taskID: " << taskID << ", threadID: " << pthread_self() << std::endl; 
         
         pthread_mutex_unlock(&mymutex);
         
         //釋放信号量,通知消費者線程
         pthread_cond_signal(&mycv);
         
         taskID ++;
 
         //休眠1秒
         sleep(1);
     }
     
     return NULL;
 }
 
 int main()
 {
     pthread_mutex_init(&mymutex, NULL);
     pthread_cond_init(&mycv, NULL);
 
     //建立5個消費者線程
     pthread_t consumerThreadID[5];
     for (int i = 0; i < 5; ++i)
         pthread_create(&consumerThreadID[i], NULL, consumer_thread, NULL);
     
     //建立一個生産者線程
     pthread_t producerThreadID;
     pthread_create(&producerThreadID, NULL, producer_thread, NULL);
 
     pthread_join(producerThreadID, NULL);
     
     for (int i = 0; i < 5; ++i)
         pthread_join(consumerThreadID[i], NULL);
     
     pthread_cond_destroy(&mycv);
     pthread_mutex_destroy(&mymutex);
 
     return 0;
 }      

怎麼樣?上述代碼如果對于新手來說,望而卻步。

為了實作這樣的功能在 Windows 上你需要掌握線程如何建立、線程同步對象 CriticalSection、Event、Semaphore、WaitForSingleObject/WaitForMultipleObjects 等作業系統對象和 API。

在 Linux 上需要掌握線程建立,你需要了解線程建立、互斥體、條件變量。

對于需要支援多個平台的開發,需要開發者同時熟悉上述原理并編寫多套适用不同平台的代碼。

C++11 的線程庫改變了這個現狀,現在你隻需要掌握 std::thread、std::mutex、std::condition_variable 少數幾個線程同步對象即可,同時使用這些對象編寫出來的代碼也可以跨平台。示例如下:

#include <thread>
 #include <mutex>
 #include <condition_variable>
 #include <list>
 #include <iostream>
 
 class Task
 {
 public:
     Task(int taskID)
     {
         this->taskID = taskID;
     }
     
     void doTask()
     {
         std::cout << "handle a task, taskID: " << taskID << ", threadID: " << std::this_thread::get_id() << std::endl; 
     }
     
 private:
     int taskID;
 };
 
 std::mutex                mymutex;
 std::list<Task*>          tasks;
 std::condition_variable   mycv;
 
 void* consumer_thread()
 {   
     Task* pTask = NULL;
     while (true)
     {
         std::unique_lock<std::mutex> guard(mymutex);
         while (tasks.empty())
         {               
             //如果獲得了互斥鎖,但是條件不合适的話,pthread_cond_wait會釋放鎖,不往下執行。
             //當發生變化後,條件合适,pthread_cond_wait将直接獲得鎖。
             mycv.wait(guard);
         }
         
         pTask = tasks.front();
         tasks.pop_front();
         
         if (pTask == NULL)
             continue;
 
         pTask->doTask();
         delete pTask;
         pTask = NULL;       
     }
     
     return NULL;
 }
 
 void* producer_thread()
 {
     int taskID = 0;
     Task* pTask = NULL;
     
     while (true)
     {
         pTask = new Task(taskID);
             
         //使用括号減小guard鎖的作用範圍
         {
             std::lock_guard<std::mutex> guard(mymutex);
             tasks.push_back(pTask);
             std::cout << "produce a task, taskID: " << taskID << ", threadID: " << std::this_thread::get_id() << std::endl; 
         }
         
         //釋放信号量,通知消費者線程
         mycv.notify_one();
         
         taskID ++;
 
         //休眠1秒
         std::this_thread::sleep_for(std::chrono::seconds(1));
     }
     
     return NULL;
 }
 
 int main()
 {
     //建立5個消費者線程
     std::thread consumer1(consumer_thread);
     std::thread consumer2(consumer_thread);
     std::thread consumer3(consumer_thread);
     std::thread consumer4(consumer_thread);
     std::thread consumer5(consumer_thread);
     
     //建立一個生産者線程
     std::thread producer(producer_thread);
 
     producer.join();
     consumer1.join();
     consumer2.join();
     consumer3.join();
     consumer4.join();
     consumer5.join();
 
     return 0;
 }      

感覺如何?代碼既簡潔又統一。

這就是 C++11 之後使用 Modern C++ 開發的效率!

C++11 之後的 C++ 更像一門新的語言。

當 C++11 的編譯器釋出之後(Visual Studio 2013、g++4.8),我第一時間更新了我的編譯器,同時把我們的項目使用了 C++11 特性進行了改造。