我們說(shuō)的 Modern C++,一般指的是 C++11 及以后的標(biāo)準(zhǔn),從 C++ 11 開(kāi)始,Modern C++ 引入了大量的實(shí)用的特性,主要是兩大方面,學(xué)習(xí)的時(shí)候也可以從這兩大方面學(xué)習(xí):
增強(qiáng)或者改善的語(yǔ)法特性;
新增的或者改善的 STL 庫(kù)。
我們來(lái)看幾個(gè)具體的案例:
案例 1:統(tǒng)一的類成員初始化語(yǔ)法與 std::initializer_list:
在 C++98/03 中,假設(shè)我們要初始化一個(gè)類數(shù)組類型的成員(例如常用的清零操作),我們需要這么寫(xiě):
class A
{
public:
A()
{
//初始化arr
arr[0] = 0;
arr[1] = 0;
arr[2] = 0;
arr[3] = 0;
}
public:
int arr[4];
};
假設(shè)數(shù)組 arr 較長(zhǎng),我們可以使用循環(huán)或者借助 memset 函數(shù)去初始化,代碼如下:
class A
{
public:
A()
{
//使用循環(huán)初始化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 中我們可以直接通過(guò)賦值操作來(lái)初始化一個(gè)數(shù)組的:
int arr[4] = { 0 };
但是對(duì)于作為類的成員變量的數(shù)組元素,C++98/03 是不允許我們這么做的。
到 C++11 中全部放開(kāi)并統(tǒng)一了,在 C++11 中我們也可以使用這樣的語(yǔ)法是初始化數(shù)組:
class A
{
public:
//在 C++11中可以使用大括號(hào)語(yǔ)法初始化數(shù)組類型的成員變量
A() : arr{0}
{
}
public:
int arr[4];
};
如果你有興趣,我們可以更進(jìn)一步:
在 C++ 98/03 標(biāo)準(zhǔn)中,對(duì)類的成員必須使用 static const 修飾,而且類型必須是整型 (包括 bool、 char、 int、 long 等),這樣才能使用這種初始化語(yǔ)法:
//C++98/03 在類定義處初始化成員變量
class A
{
public:
//T 的類型必須是整型,且必須使用 static const 修飾
static const T t = 某個(gè)整型值;
};
在 C++11 標(biāo)準(zhǔn)中就沒(méi)有這種限制了,我們可以使用花括號(hào)(即{})對(duì)任意類型的變 量進(jìn)行初始化,而且不用是 static 類型:
//C++ 11 在類定義處初始化成員變量
class A
{
public:
//有沒(méi)有一種Java初始化類成員變量的即視感^ _ ^
bool ma{true};
int mb{2019};
std::string mc{“helloworld”};
};
當(dāng)然,在實(shí)際開(kāi)發(fā)中,建議還是將這些成員變量的初始化統(tǒng)一寫(xiě)到構(gòu)造函數(shù)的初始化列表中,方便閱讀和維護(hù)代碼。
案例 2:注解標(biāo)簽
C++ 14 引入了 [[deprecated]] 標(biāo)簽來(lái)表示一個(gè)函數(shù)或者類型等已被棄用,在使用這些被棄用的函數(shù)或者類型并編譯時(shí), 編譯器會(huì)給出相應(yīng)的警告, 有的編譯器直接生成編譯錯(cuò)誤:
[[deprecated]] void funcX();
這個(gè)標(biāo)簽在實(shí)際開(kāi)發(fā)中非常有用,尤其在設(shè)計(jì)一些庫(kù)代碼時(shí),如果庫(kù)作者希望某個(gè)函數(shù)或者類型不想再被用戶使用,則可以使用該標(biāo)注標(biāo)記。當(dāng)然,我們也可以使用如下語(yǔ)法給出編譯時(shí)的具體警告或者出錯(cuò)信息:
[[deprecated(“use funY instead”)]] void funcX();
有如下代碼:
#include 《iostream》
[[deprecated(“use funcY instead”)]] void funcX()
{
//實(shí)現(xiàn)省略
}
int main()
{
funcX();
return 0;
}
若在 main 函數(shù)中調(diào)用被標(biāo)記為 deprecated 的函數(shù) funcX,則在 gcc/g++7.3 中編譯時(shí)會(huì)得到如下警告信息:
[root@myaliyun testmybook]# g++ -g -o test_attributes test_attributes.cpp
test_attributes.cpp: In function ‘int main()’:
test_attributes.cpp11: warning: ‘void funcX()’ is deprecated: use funcY instead
[-Wdeprecated-declarations]
funcX();
^
test_attributes.cpp42: note: declared here
[[deprecated(“use funcY instead”)]] void funcX()
Java 開(kāi)發(fā)者對(duì)這個(gè)標(biāo)注應(yīng)該再熟悉不過(guò)了。在 Java 中使用@Deprecated 標(biāo)注可以達(dá)到同樣的效果,這大概是 C++標(biāo)準(zhǔn)委員“拖欠”廣大 C++開(kāi)發(fā)者太久的一個(gè)特性吧。
C++ 17 提供了三個(gè)實(shí)用注解:[[fallthrough]]、 [[nodiscard]] 和 [[maybe_unused]],這里 逐一介紹它們的用法。
[[fallthrough]] 用于 switch-case 語(yǔ)句中,在某個(gè) case 分支執(zhí)行完畢后如果沒(méi)有 break 語(yǔ)句,則編譯器可能會(huì)給出一條警告。但有時(shí)這可能是開(kāi)發(fā)者有意為之的。為了讓編譯器明確知道開(kāi)發(fā)者的意圖,可以在需要某個(gè) case 分支被“貫穿”的地方(上一個(gè) case 沒(méi)有break 語(yǔ)句)顯式設(shè)置 [[fallthrough]] 標(biāo)記。代碼示例如下:
switch (type)
{
case 1:
func1();
//這個(gè)位置缺少 break 語(yǔ)句,且沒(méi)有 fallthrough 標(biāo)注,
//可能是一個(gè)邏輯錯(cuò)誤,在編譯時(shí)編譯器可能會(huì)給出警告,以提醒修改
case 2:
func2();
//這里也缺少 break 語(yǔ)句,但是使用了 fallthrough 標(biāo)注,
//說(shuō)明是開(kāi)發(fā)者有意為之的,編譯器不會(huì)給出任何警告
[[fallthrough]];
case 3:
func3();
}
注意:在 gcc/g++中, [[fallthrough]] 后面的分號(hào)不是必需的,在 Visual Studio 中必須加上分號(hào),否則無(wú)法編譯通過(guò)。
熟悉 Golang 的讀者,可能對(duì) fallthrough 這一語(yǔ)法特性非常熟悉, Golang 中在 switch-case 后加上 fallthrough,是一個(gè)常用的告訴編譯器意圖的語(yǔ)法規(guī)則。代碼示例如下:
//以下是 Golang 語(yǔ)法
s := “abcd”
switch s[3] {
case ‘a(chǎn)’:
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]] 一般用于修飾函數(shù),告訴函數(shù)調(diào)用者必須關(guān)注該函數(shù)的返回值(即不能丟棄該函數(shù)的返回值)。如果函數(shù)調(diào)用者未將該函數(shù)的返回值賦值給一個(gè)變量,則編譯器會(huì)給出一個(gè)警告。例如,假設(shè)有一個(gè)網(wǎng)絡(luò)連接函數(shù) connect,我們通過(guò)返回值明確說(shuō)明了連接是否建立成功,則為了防止調(diào)用者在使用時(shí)直接將該值丟棄,我們可以將該函數(shù)使用 [[nodiscard]] 標(biāo)記:
[[nodiscard]] int connect(const char* address, short port)
{
//實(shí)現(xiàn)省略
}
int main()
{
//忽略了connect函數(shù)的返回值,編譯器會(huì)給出一個(gè)警告
connect(“127.0.0.1”, 8888);
return 0;
}
在 C++ 20 中,對(duì)于諸如 operator new()、 std::allocate()等庫(kù)函數(shù)均使用了 [[nodiscard]] 進(jìn)行標(biāo)記,以強(qiáng)調(diào)必須使用這些函數(shù)的返回值。
再來(lái)看另外一個(gè)標(biāo)記。
在通常情況下,編譯器會(huì)對(duì)程序代碼中未使用的函數(shù)或變量給出警告,另一些編譯器干脆不允許通過(guò)編譯。在 C++ 17 之前,程序員為了消除這些未使用的變量帶來(lái)的編譯警告或者錯(cuò)誤,要么修改編譯器的警告選項(xiàng)設(shè)置,要么定義一個(gè)類似于 UNREFERENCED_PARAMETER 的宏來(lái)顯式調(diào)用這些未使用的變量一次,以消除編譯警告或錯(cuò)誤:
#define UNREFERENCED_PARAMETER(x) x
int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
//C++17之前為了消除編譯器對(duì)未使用的變量hPrevInstance、lpCmdLine給出的警告,我們可以這么做
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
//無(wú)關(guān)代碼省略
}
以上代碼節(jié)選自一個(gè)標(biāo)準(zhǔn) Win32 程序的結(jié)構(gòu),其中的函數(shù)參數(shù) hPrevInstance 和 lpCmdLine 一般不會(huì)被用到,編譯器會(huì)給出警告。為了消除這類警告,這里定義了一個(gè)宏 UNREFERENCED_PARAMETER 并進(jìn)行調(diào)用,造成這兩個(gè)參數(shù)被使用的假象。
C++17 有了 [[maybe_unused]] 注解之后,我們就再也不需要這類宏來(lái)“欺騙”編譯器了。以上代碼使用該注解后可以修改如下:
int APIENTRY wWinMain(HINSTANCE hInstance,
[[maybe_unused]] HINSTANCE hPrevInstance,
[[maybe_unused]] LPWSTR lpCmdLine,
int nCmdShow)
{
//無(wú)關(guān)代碼省略
}
案例 3:final、 override 關(guān)鍵字和 =default、 =delete 語(yǔ)法
3.1 final 關(guān)鍵字
在 C++11 之前,我們沒(méi)有特別好的方法阻止一個(gè)類被其他類繼承,到了 C++11 有了 final 關(guān)鍵字我們就可以做到了。final 關(guān)鍵字修飾一個(gè)類,這個(gè)類將不允許被繼承,這在其他語(yǔ)言(如 Java)中早就實(shí)現(xiàn)了。在 C++ 11 中, final 關(guān)鍵字要寫(xiě)在類名的后面,這在其他語(yǔ)言中是寫(xiě)在 class 關(guān)鍵字前面的。示例如下:
class A final
{
};
class B : A
{
};
由于類 A 被聲明成 final, B 繼承 A, 所以編譯器會(huì)報(bào)如下錯(cuò)誤提示類 A 不能被繼承:
error C3246: ‘B’ : cannot inherit from ‘A’ as it has been declared as ‘final’
3.2 override 關(guān)鍵字
C++98/03 語(yǔ)法規(guī)定,在父類中加了 virtual 關(guān)鍵字的方法可以被子類重寫(xiě),子類重寫(xiě)該方法時(shí)可以加或不加 virtual 關(guān)鍵字,例如下面這樣:
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)
{
}
};
這種寬松的規(guī)定可能會(huì)帶來(lái)以下兩個(gè)問(wèn)題。
當(dāng)我們閱讀代碼時(shí),無(wú)論子類重寫(xiě)的方法是否添加了 virtual 關(guān)鍵字,我們都無(wú)法 直觀地確定該方法是否是重寫(xiě)的父類方法。
如果我們?cè)谧宇愔胁恍⌒膶?xiě)錯(cuò)了需要重寫(xiě)的方法的函數(shù)簽名(可能是參數(shù)類型、 個(gè)數(shù)或返回值類型),這個(gè)方法就會(huì)變成一個(gè)獨(dú)立的方法,這可能會(huì)違背我們重寫(xiě) 父類某個(gè)方法的初衷,而編譯器在編譯時(shí)并不會(huì)檢查到這個(gè)錯(cuò)誤。
為了解決以上兩個(gè)問(wèn)題, C++11 引進(jìn)了 override 關(guān)鍵字,其實(shí) override 關(guān)鍵字并不是新語(yǔ)法,在 Java 等其他編程語(yǔ)言中早就支持。類方法被 override 關(guān)鍵字修飾,表明該方法重寫(xiě)了父類的同名方法,加了該關(guān)鍵字后,編譯器會(huì)在編譯階段做相應(yīng)的檢查,如果其父類不存在相同簽名格式的類方法,編譯器就會(huì)給出相應(yīng)的錯(cuò)誤提示。
情形一,父類不存在,子類標(biāo)記了 override 的方法:
class A
{
};
class B : A
{
protected:
void func(int k, int d) override
{
}
};
由于在父類 A 中沒(méi)有 func 方法,所以編譯器會(huì)提示錯(cuò)誤:
error C3668: ‘B::func’ : method with override specifier ‘override’ did not override
any base class methods
情形二,父類存在,子類標(biāo)記了 override 的方法,但函數(shù)簽名不一致:
class A
{
protected:
virtual int func(int k, int d)
{
return 0;
}
};
class B : A
{
protected:
virtual void func(int k, int d) override
{
}
};
上述代碼編譯器會(huì)報(bào)同樣的錯(cuò)誤。正確的代碼如下:
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 語(yǔ)法
如果一個(gè) C++類沒(méi)有顯式給出構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、 operator= 這幾類函數(shù)的實(shí)現(xiàn),則在需要它們時(shí),編譯器會(huì)自動(dòng)生成;或者,在給出這些函數(shù)的聲明時(shí),如果沒(méi)有給出其實(shí)現(xiàn),則編譯器在鏈接時(shí)會(huì)報(bào)錯(cuò)。如果使用=default 標(biāo)記這類函數(shù),則編譯器會(huì)給出默認(rèn)的實(shí)現(xiàn)。來(lái)看一個(gè)例子:
class A
{
};
int main()
{
A a;
return 0;
}
這樣的代碼是可以編譯通過(guò)的,因?yàn)榫幾g器默認(rèn)生成 A 的一個(gè)無(wú)參構(gòu)造函數(shù),假設(shè)我們現(xiàn)在向 A 提供一個(gè)有參構(gòu)造函數(shù):
class A
{
public:
A(int i)
{
}
};
int main()
{
A a;
return 0;
}
這時(shí),編譯器就不會(huì)自動(dòng)生成默認(rèn)的無(wú)參構(gòu)造函數(shù)了,這段代碼會(huì)編譯出錯(cuò),提示 A 沒(méi)有合適的無(wú)參構(gòu)造函數(shù):
error C2512: ‘A’ : no appropriate default constructor available
我們這時(shí)可以手動(dòng)為 A 加上無(wú)參構(gòu)造函數(shù), 也可以使用=default 語(yǔ)法強(qiáng)行讓編譯器自己生成:
class A
{
public:
A() = default;
A(int i)
{
}
};
int main()
{
A a;
return 0;
}
=default 最大的作用可能是在開(kāi)發(fā)中簡(jiǎn)化了構(gòu)造函數(shù)中沒(méi)有實(shí)際初始化代碼的寫(xiě)法,尤其是聲明和實(shí)現(xiàn)分別屬于.h 和.cpp 文件。例如,對(duì)于類 A,其頭文件為 a.h,其實(shí)現(xiàn)文件為 a.cpp,則正常情況下我們需要在 a.cpp 文件中寫(xiě)其構(gòu)造函數(shù)和析構(gòu)函數(shù)的實(shí)現(xiàn)(可能沒(méi)有實(shí)際的構(gòu)造和析構(gòu)邏輯):
//a.h
class A
{
public:
A();
~A();
};
//a.cpp
#include “a.h”
A::A()
{
}
A::~A()
{
}
可以發(fā)現(xiàn),即使在 A 的構(gòu)造函數(shù)和析構(gòu)函數(shù)中什么邏輯也沒(méi)有,我們還是不得不在 a.cpp 中寫(xiě)上構(gòu)造函數(shù)和析構(gòu)函數(shù)的實(shí)現(xiàn)。有了=default 關(guān)鍵字,我們就可以在 a.h 中直接寫(xiě)成:
//a.h
class A
{
public:
A() = default;
~A() = default;
};
//a.cpp
#include “a.h”
//在 cpp 文件中就不用再寫(xiě) A 的構(gòu)造函數(shù)和析構(gòu)函數(shù)的實(shí)現(xiàn)了
3.4 =delete 語(yǔ)法
既然有強(qiáng)制讓編譯器生成構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、 operator=的語(yǔ)法,那么也應(yīng)該有禁止編譯器生成這些函數(shù)的語(yǔ)法,沒(méi)錯(cuò),就是 =delete。在 C++ 98/03 規(guī)范中, 如果我們想讓一個(gè)類不能被拷貝(即不能調(diào)用其拷貝構(gòu)造函數(shù)),則可以將其拷貝構(gòu)造函數(shù)和 operator=函數(shù)定義成 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;
}
通過(guò)以上代碼利用 a1 構(gòu)造 a2 時(shí),編譯器會(huì)提示錯(cuò)誤:
error C2248: ‘A::A’ : cannot access private member declared in class ‘A’
error C2248: ‘A::operator =’ : cannot access private member declared in class ‘A’
我們利用這種方式間接實(shí)現(xiàn)了一個(gè)類不能被拷貝的功能,這也是繼承自 boost::noncopyable 的類不能被拷貝的實(shí)現(xiàn)原理。現(xiàn)在有了=delete語(yǔ)法,我們直接使用該語(yǔ)法禁止編譯器生成這兩個(gè)函數(shù)即可:
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;
}
一般在一些工具類中, 我們不需要用到構(gòu)造函數(shù)、 析構(gòu)函數(shù)、 拷貝構(gòu)造函數(shù)、 operator= 這 4 個(gè)函數(shù),為了防止編譯器自己生成,同時(shí)為了減小生成的可執(zhí)行文件的體積,建議使用=delete 語(yǔ)法禁止編譯器為這 4 個(gè)函數(shù)生成默認(rèn)的實(shí)現(xiàn)代碼,例如:
//這是一個(gè)字符轉(zhuǎn)碼工具類
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:對(duì)多線程的支持
我們來(lái)看一個(gè)稍微復(fù)雜一點(diǎn)的例子。
在 C++11 之前,由于 C++98/03 本身缺乏對(duì)線程和線程同步原語(yǔ)的支持,我們要寫(xiě)一個(gè)生產(chǎn)者消費(fèi)者邏輯要這么寫(xiě)。
在 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)
{
//消費(fèi)者從隊(duì)列m_arrItem中取出任務(wù)執(zhí)行
lpMsg = m_arrItem[0];
m_arrItem.erase(m_arrItem.begin() + 0);
}
::LeaveCriticalSection(&m_csItem);
if (NULL == lpMsg)
continue;
//處理任務(wù)
HandleMsg(lpMsg);
delete lpMsg;
}
return 0;
}
int CRecvMsgTask::Stop()
{
m_HttpClient.SetCancalEvent();
::SetEvent(m_hEvent);
return 0;
}
void CRecvMsgTask::TaskFinish()
{
}
//生產(chǎn)者調(diào)用這個(gè)方法將Task放入隊(duì)列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會(huì)釋放鎖,不往下執(zhí)行。
//當(dāng)發(fā)生變化后,條件合適,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);
//釋放信號(hào)量,通知消費(fèi)者線程
pthread_cond_signal(&mycv);
taskID ++;
//休眠1秒
sleep(1);
}
return NULL;
}
int main()
{
pthread_mutex_init(&mymutex, NULL);
pthread_cond_init(&mycv, NULL);
//創(chuàng)建5個(gè)消費(fèi)者線程
pthread_t consumerThreadID[5];
for (int i = 0; i 《 5; ++i)
pthread_create(&consumerThreadID[i], NULL, consumer_thread, NULL);
//創(chuàng)建一個(gè)生產(chǎn)者線程
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;
}
怎么樣?上述代碼如果對(duì)于新手來(lái)說(shuō),望而卻步。
為了實(shí)現(xiàn)這樣的功能在 Windows 上你需要掌握線程如何創(chuàng)建、線程同步對(duì)象 CriticalSection、Event、Semaphore、WaitForSingleObject/WaitForMultipleObjects 等操作系統(tǒng)對(duì)象和 API。
在 Linux 上需要掌握線程創(chuàng)建,你需要了解線程創(chuàng)建、互斥體、條件變量。
對(duì)于需要支持多個(gè)平臺(tái)的開(kāi)發(fā),需要開(kāi)發(fā)者同時(shí)熟悉上述原理并編寫(xiě)多套適用不同平臺(tái)的代碼。
C++11 的線程庫(kù)改變了這個(gè)現(xiàn)狀,現(xiàn)在你只需要掌握 std::thread、std::mutex、std::condition_variable 少數(shù)幾個(gè)線程同步對(duì)象即可,同時(shí)使用這些對(duì)象編寫(xiě)出來(lái)的代碼也可以跨平臺(tái)。示例如下:
#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::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會(huì)釋放鎖,不往下執(zhí)行。
//當(dāng)發(fā)生變化后,條件合適,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);
//使用括號(hào)減小guard鎖的作用范圍
{
std::lock_guard《std::mutex》 guard(mymutex);
tasks.push_back(pTask);
std::cout 《《 “produce a task, taskID: ” 《《 taskID 《《 “, threadID: ” 《《 std::get_id() 《《 std::endl;
}
//釋放信號(hào)量,通知消費(fèi)者線程
mycv.notify_one();
taskID ++;
//休眠1秒
std::seconds(1));
}
return NULL;
}
int main()
{
//創(chuàng)建5個(gè)消費(fèi)者線程
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);
//創(chuàng)建一個(gè)生產(chǎn)者線程
std::thread producer(producer_thread);
producer.join();
consumer1.join();
consumer2.join();
consumer3.join();
consumer4.join();
consumer5.join();
return 0;
}
感覺(jué)如何?代碼既簡(jiǎn)潔又統(tǒng)一。
這就是 C++11 之后使用 Modern C++ 開(kāi)發(fā)的效率!
C++11 之后的 C++ 更像一門(mén)新的語(yǔ)言。
當(dāng) C++11 的編譯器發(fā)布之后(Visual Studio 2013、g++4.8),我第一時(shí)間更新了我的編譯器,同時(shí)把我們的項(xiàng)目使用了 C++11 特性進(jìn)行了改造。
當(dāng)然,例子還有很多,限于文章篇幅,這里就列舉 4 個(gè)案例。
編輯:jq
-
C++
+關(guān)注
關(guān)注
22文章
2117瀏覽量
74754 -
編譯器
+關(guān)注
關(guān)注
1文章
1654瀏覽量
49844
原文標(biāo)題:提升 C++ 開(kāi)發(fā)效率的幾個(gè)小技巧
文章出處:【微信號(hào):gh_3980db2283cd,微信公眾號(hào):開(kāi)關(guān)電源芯片】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
主流的 MCU 開(kāi)發(fā)語(yǔ)言為什么是 C 而不是 C++?

關(guān)于FLASHXIP下載問(wèn)題求解
小程序開(kāi)發(fā)必須知道的5個(gè)技巧:提升效率與用戶體驗(yàn)的權(quán)威指南
C++學(xué)到什么程度可以找工作?
源代碼加密、源代碼防泄漏c/c++與git服務(wù)器開(kāi)發(fā)環(huán)境

測(cè)的值不定然后開(kāi)始減小直到為0,不知道怎么回事?
Spire.XLS for C++組件說(shuō)明

AKI跨語(yǔ)言調(diào)用庫(kù)神助攻C/C++代碼遷移至HarmonyOS NEXT
關(guān)于陶瓷電路板你不知道的事

用GNU構(gòu)建裸機(jī)系統(tǒng)
又一電工不知道,施耐德變頻器怎么復(fù)位,如果不告訴你,你知道怎么復(fù)位嗎?

AMC1100使用前需要烘烤,不知道烘烤溫度和烘烤時(shí)間是多少?
OpenVINO2024 C++推理使用技巧
C++中實(shí)現(xiàn)類似instanceof的方法

評(píng)論