【深刻理解C++11【5】】linux
一、原子操做與C++11原子類型ios
C++98 中的原子操做、mutex、pthread:編程
#include<pthread.h> #include <iostream> using namespace std; static long long total = 0; pthread_mutex_t m = PHTREAD_MUTEX_INITIALIZER; void* func(void*){ long long i; for(i=0;i < 100000000LL; i++){ pthread_mutex_lock(&m); total+=i; pthread_mutex_unlock(&m); } } int main() { pthread_t thread1, thread2; if (pthread_create(&thread1, NULL, &func, NULL)){ throw; } if(pthread_create(&thread2, NULL, &func, NULL)){\ throw; } pthread_join(thread1, NULL); pthread_join(thread2, NULL); cout<<total<<endl; return 0; }
C++11中引入 了 std::thread對,以及 atomic_llong原子類型。使得程序更加簡練。windows
#include<atomic> #include<thread> #include<iostream> using namespace std; atomic_llong total{0}; // 原子數據類型 void func(int){ for(long long i = 0;i < 100000000LL; ++i){ total += i; } } int main(){ thread t1(func1, 0); thread t2(func2, 0); t1.join(); t2.join(); count<<total<<endl; return 0; }
<cstdatomic>中原子顧炎武 和內置類型對應表:數組
更爲廣泛地,可使用 atomic類模板,經過該類模板,能夠任意定義出須要的原子類型。ide
std:: atomic< T> t;
C++11中,不容許對原子類型進行拷貝構造、移動構造,以及使用operator=等。老是默認被刪除的。函數
atomic<float> af{1.2f}; atomic<float> af1{af}; // 編譯錯誤
對原子類型解原子則是徹底OK的。性能
atomic<float> af{1.2f}; float f = af; float f1{af};
原子類型是由編譯器來保證針對原子類型數據的操做都是原子操做。原子操做都是平臺相關的,C++11中定義出了統一的接口,根據編譯選項產生其平臺相關的實現。學習
大多數的原子類型,均可以執行讀(load)、寫(store)、交換(exchange)。優化
atomic<int> a; int b = a; // 等同於 b = a.load(); atomic<int> a; a = 1; // 等同於 a.store(1);
atomic_flag 比較特殊,不須要使用load、store等成員函數。經過 test_and_set 以及 clear,能夠實現一個 spin lock。
atomic_flag 提供了無鎖編程。無鎖編程能夠最大限度地挖掘並行編程的性能。
二、內存模型,順序 一致性與 memory_order
編譯器或處理器可能會改變代碼的執行順序。默認狀況下C++11中的原子類型的變量在線程中老是保持着順序執行的特性(非原子類型沒有必要,由於不須要在線程間同步)。咱們稱這樣的特性爲「順序一致」。
PowerPC、ArmV7 是弱內存模型構架。x86是強順序內存模型平臺。
內存柵欄(memory barrier)sync 對高度流水化的PowerPC處理器的性能影響很大。
memory_order_relaxed,表示使用鬆散的內存模型,該指令能夠任由編譯器重排序或者由處理器亂序執行。以下:
#include <thread> #include <atomic> #include <iostream> using namespace std; atomic<int> a{0}; atomic<int> b{0}; int ValueSet (int){ int t = 1; a.store(t, memory_order_relaxed); b.store(2, memory_order_relaxed); } int Observer(int){ cout<<a<<b<<endl; } int main() { thread t1(ValueSet, 0); thread t2(Observer, 0); t1.join(); t2.join(); cout<<a<<b<<endl; return 0; }
C++11 中一共有7種 memory_order的枚舉值 :
store可使用:memory_order_relaxed、memory_order_release、memory_order_seq_cst。
load可使用:memory_order_relaxed、memory_order_consume、memory_order_acquire。
memory_order_seq_cst 是C++11中的默認值。
memory_order_release、memory_order_acquire經常結合使用,稱爲release-acquire內存順序。
#include <thread> #include <atomic> #include <iostream> using namespace std; atomic<int> a{0}; atomic<int> b{0}; int Thread1 (int){ int t = 1; a.store(t, memory_order_relaxed); b.store(2, memory_order_release); // 本原子操做前全部的寫原子操做必須完成。 } int Thread2(int){ while(b.load(memory_order_acquire)!=2) // 本原子操做完成才能執行以後全部的讀原子操做 cout<<a.load(memory_order_relaxed)<<endl; // 1 } int main() { thread t1(Thread1, 0); thread t2(Thread2, 0); t1.join(); t2.join(); return 0; }
順序一致、鬆散、release-acquire、release-consume 一般是最爲典型的4種內存順序。
三、線程局部存儲(TLS,thread local storage)
C++98 中各個編譯器公司都 本身的TLS標準。 g++/clang++ 中能夠看到以下的語法:
__thread int errCode;
C++11對TLS作出了統一的規定。經過 thread_local 來聲明 TLS變量。
int thread_local errCode。
線程結束時,TLS變量將再也不有效。對TLS變量取值 &,也只能夠得到當前線程中的TLS變量的地址值。
四、quick_exit、at_quick_exit
terminate函數內部會調用 abort函數,一般指發生異常退出。
abort會發送 SIGABRT,從而被操做系統幹掉。
exit、atexit來源於C。exit()屬於正常退出,會調用自動變量的析構函數,以及atexit註冊的函數。註冊函數的調用次序與註冊順序相反,如:
#include <cstdlib> #include <iostream> using namespace std; void openDevice() { cout<<"device is opened."<<endl; } void resetDeviceStat(){ cout<<"device stat is reset."<<endl; } void closeDevice(){ cout<<"device is closed."<<endl; } int main() { atexit(closeDevice); atexit(resetDeviceStat); openDevice(); exit(0); }
device is opened device stat is reset device is closed
C++11 中引入了 quick_exit 函數。該函數並不執行析構函數,而只是使程序終止。quick_exit 是正常退出。at_quick_exit 也能夠註冊函數。標準要求編譯器至少支持32個註冊函數的調用。
#include <cstdlib> #include <iostream> using namespace std; struct A{~A(){cout<<"Des A"<<endl;}} void closeDevice(){cout<<"closed."<<endl;} int main() { A a; at_quick_exit(closeDevice); quick_exit(0); }
上面代碼,變量a的析構函數不會被調用。
五、nullptr
C++98中的NULL以下:
#undef NULL #if defined(__cplusplus) #define NULL 0 #else #define NULL ((void*)0) #endif
按上述定義,會有以下的經典問題:
#include<stdio.h> void f(char* c){ printf("invoke f(char*)\n"); } void f(int i ){ printf("invoke f(int)\n"); } int main() { f(0); f(NULL); f((char*)0); } // output // invoke f(int) // invoke f(int) // invoke f(char*)
C++中引入了 nullptr,其類型爲 nullptr_t,經過 nullptr_t能夠聲明其餘的指針空值類型。
typedef decltype(nullptr) nullptr_t;
nullptr 能夠解決C++98 中的 NULL致使的調用整數的問題。
nullptr 有以下特性:
1)nullptr_t類型數據能夠隱匿轉換成任意一個指針類型。
2)nullptr_t 不能轉換爲非指針類型。
3)nullptr_t 不適用於算術運算表達式。
4)nullptr_t 能夠運用關係運算符。
int main() { // nullptr 可隱匿轉換爲 char* char *cp = nullptr; // 不可轉換爲整形,而任何類型也不能轉換爲 nullptr_t // 如下代碼不能經過編譯 // int n1 = nullptr; // int n2 = reinterpret_cast<int>(nullptr); // nullptr 與 nullptr_t 類型變量能夠做比較 // 當使用 ==、<=、>=符號比較時返回true nullptr_t nptr; if (nptr==nullptr) cout<<"=="<<endl; else cout<<"!="<<endl; if (nptr<nullptr) cout<<"<"<<endl; else cout<<"!<"<endl; // 不能轉換爲整形或bool類型,如下代碼不能經過編譯。 // if (0==nullptr) // if (nullptr); // 不能夠算術運算,如下代碼不能經過編譯 // nullptr += 1; // nullptr * 5; // 如下操做都可以正常進行 sizeof(nullptr); typeid(nullptr); throw(nullptr); return 0; } // output // nullptr_t nptr == nullptr // nullptr_t nptr !< nullptr // terminate called after throwing an instance of 'decltype(nullptr)' // Aborted
當nullptr_t與模板結合使用時,會被當成普通類型,而不是一個指針。
template<typename T> void g(T* t) {} template<typename T> void h(T t) {} int main() { g(nullptr); //編譯失敗,nullptr不被認爲是指針。 g((float*)nullptr); // 推導出 T = float h(0); // 推導出 T = int h(nullptr); // 推導出 T = nullptr_t h((flaot*)nullptr) // 推導出 T = float * }
六、一些關於nullptr規則的討論。
nullptr類型數據所佔用的內存空間大小跟void*相同。
sizeof(nullptr_t) == sizeof(void*)
nullptr 的轉換是隱式的,(void*)0 則必須通過類型轉換。
int foo() { int* px = (void*)0; int* py = nullptr; }
nullptr_t對象 的地址能夠被用戶使用,但不能獲取 nullptr 的地址,但能夠聲明 nullptr 的右值引用。
int main() { // 能夠取 nullptr_t 對象的地址 nullptr_t my_null; printf("%x\n", &my_null); }
七、類與默認函數
C++98 中一理有了自定義版本的構造函數,就會致使咱們定義的類型再也不是POD。
class TwoCstor{ public: // TwoCstor再也不是POD類型 TwoCstor(){} TwoCstor(int i):data(i){} private: int data; }
C++11中提供了=default功能,指示編譯器生成該函數的默認版本。
class TwoCstor{ public: // TwoCstor依然是POD類型 TwoCstor() = default; TwoCstor(int i):data(i){} private: int data; }
C++11中還提供了 =delete功能,提示編譯器不生成函數的缺省版本。
class NoCopyCstor{ public: NoCopyCstor() = default; NoCopyCstor(const NoCopyCstor & ) = delete; }
一理缺省版本 delete了,重載該函數也是非法的。
八、=default 與 =delete
=default 修飾的函數爲顯式缺省(explicit defaulted)函數
=delete 修飾的函數爲刪除(deleted )函數
=defalut不只能夠用於類型的定義中,也能夠用於類的實現中,這樣的好處能夠用於方便地用於多個版本的管理。
class DefaultedOptr{ public: DefaultedOptr & operator = (const DefaultedOptr &); } inline DefaultedOptr & DefaultedOptr::operator = (const DefaultedOptr &) = default;
有一些非缺省函數,若是若是加上=defalut,則編譯器會按照某些標準行爲爲其生成代碼。
=delete 能夠避免編譯器作一些沒必要要的隱匿數據類型轉換。
class ConvType{ public: ConvType(int i){} ConvType(char c)=delete; // 刪除char版本 }; void Func(ConvType ct){} int main() { Func(3); Func('a'); // 編譯失敗 ConvType ci(3); ConvType cc('a'); // 編譯失敗 }
編譯器發現從 char 構造 ConvType的構造函數被delete了,從而產面上面的編譯錯誤。
加上 explicit 後會更精妙 。
class ConvType{ public: ConvType(int i){} explicit ConvType(char c)=delete; // 刪除char版本 }; void Func(ConvType ct){} int main() { Func(3); Func('a'); // 陶然式轉換,編譯經過 ConvType ci(3); ConvType cc('a'); // 顯式轉換,編譯失敗 }
若是將 operator new 刪除,則能夠禁止 new 該類型對象。
#include <cstddef> class NoHeapAlloc{ public: void* operator new(std::size_t)=delete; } int main() { NoHeapAlloc nha; NoHeapAlloc* pnha = new NoHeapAlloc; // 編譯失敗 return 1; }
九、C++11 中的 lambda 函數
lambda函數跟普通函數相比,不須要定義函數名,取而代之的多了一對方括號[].。lambda函數的語法定義以下:
[capture](parameters) mutable -> return-type {statement}
能夠省略的內容:
1)若是不須要參數傳遞,則parameters連同()能夠一塊兒加省略。
2)默認狀況下,lambda函數是一個const函數,mutalbe可能省略。但在使用了 mutalbe時,(parameters)不能爲空,即便參數爲空。
3)不須要返回值時,return-type能夠省略。此外,在返回類型明確的狀況下,也能夠省略該部分,讓編譯器自行推導。
綜上,在最極端 狀況下,最爲簡陋的lambda函數以下:
[]{}
下面是一些lambda函數的例子。
int main() { [](); int a = 3; int b = 4; [=]{return a+b;}; // 省略了參數列表和返回類型,返回類型被推斷爲int auto func1 = [&](int c){b=a+c;}; //省略了返回類型,無返回值 auto func2 = [=,&b](int c)->int{return b+=a+c;} // 較完整的lambda函數。 }
[this] 表示值傳遞的方式近現代史當前的 this 指針。 後句列表不容許 變量重複傳遞,不然 會致使編譯時錯誤。
[=,a] // 編譯錯誤,這裏=已經以值傳遞的方式捕捉了全部變量,a重複。 [&,&this] // 編譯錯誤,這裏&已經以引用傳遞方式捕捉了全部變量,再捕捉this也是一種重複。
塊做用域{} 外也能夠定義lambda,此時捕捉列表必須爲空。
十、lambda 與仿函數
仿函數(functor)是定義了成員函數 operator() 的類型對象。仿函數是編譯器實現lambda的一種方式。lambda是仿函數的一種語法甜點。
局部函數(local function / nested function) 可以訪問父做用域的變量。C++中沒有局部函數,而是以仿函數/lambda來實現了相似功能。
十一、關於lambda的一些問題及有趣的實驗。
C++11標準容許lambda轉換爲函數指針,但不容許函數指針轉換爲lambda。
int main() { int girls = 3, boys = 4; auto totalChild = [](int x, int y) ->int{return x+y;}; typedef int (*allChild)(int x, int y); typedef int (*oneChild)(int x); allChild p; p = totalChild; oneChild q; q = totalChild; // 編譯失敗,參數類型不一致. decltype(totalChild) allPeople = totalChild; decltype(totalChild) totalPeople = p; // 指針類型沒法轉換爲 lambda. return 0; }
按值傳遞方式捕捉的變量是lambda函數中不可更改的常量。
十二、lambda 與 STL
使用STL,代碼量大,須要學習仿函數;而使用STL,代碼簡單,不須要前置學習。
1三、更多的一些關於lambda的討論
[] 只能捕捉父做用域的自動變量,超出這個範圍就不能捕捉,如全局變量。下面的代碼,一些嚴格的編譯器會產生編譯錯誤。
int d = 0; int TryCapture(){ auto ill_lambda = [d]{}; }
若是要捕捉全局變量,可使用仿函數。
1四、數據對齊
sizeof() 返回類型大小,offsetof() 返回成員變量的偏移。一個示例以下:
struct HowManyBytes{ char a; int b; }; int main() { cout<<sizeof(char)<<endl; // 1 cout<<sizeof(int)<<endl; // 4 cout<<sizeof(HowManyBytes)<<endl; // 8 cout<<offsetof(HowManyBytes, a)<<endl; // 0 cout<<offsetof(HowManyBytes, b)<<endl; // 4 }
C++ 中每一個類型的數據除去長度等屬性外,還有一項被隱藏的屬性,那就是對齊方式。數據的起始地址必須是對齊地址的倍數。
在有些平臺上,硬件沒法讀取不按照字節對齊的某些類型的數據,這個時候會拋出異常來終止程序。另外,在一些平臺上會形成數據讀取效率降低。
C++11 中添加了 alignof() 來查看數據的對齊方式,以及 alignas() 來改變類型的對齊方式。
struct alignas(32) ColorVector { double r; double g; double b; double a; } alignof(ColorVector):32
C++ 中的 alignof()、alignas()。alignof返回值是 std::size_t類型的常量。注意,數組的alignof與元素要求相同,以下:
class InComplete; // alignof 編譯錯誤,類型不完整 struct Completed{}; // alignof 1 int main() { int a; // alignof 4 long long b; // alignof 8 auto& c = b; // alignof 8 char d[1024]; // alignof 1,與元素要求相同 }
alignas 也能夠用在通常類型上。
alignas(double) char c; alignas(alignof(double)) char c;
C++98 中使用 __attribute__((__aligned__(8))) 來修改對齊方式。
通常狀況下,最大標量類型是 long double,其對齊值能夠經過 alignof(std::max_align_t)來查詢,這也叫作基本對齊值(fundamental alignment)。
容量固定,可是對齊方式變化的泛型:
template<typename T> class FixedCapacityArray { public: void push_back(T t){} char alignas(T) data[1024] = {0}; int length = 1024/sizeof(T); }
C++11 在STL庫中,添加了 std::align 函數來動態地根據指定的對齊方式調整數據塊的位置。
void* align(std::size_t alignment, std::size_t size, void*& ptr, std::size_t& space);
C++11 還提供了 aligned_storage、aligned_union,來幫助分配對齊的內存塊。
template<std::size_t Len, std;:size_t Align =default-alighment> struct aligned_storage; template<std::size_t Len, class... Types> strcut aligned_union;
下面的代碼,可能在老舊處理器上引發崩潰。
int main() { char* pchar = (char*)malloc(100); pchar++; int* pint = (int*)pchar; // 此處 pint 可能指向非對齊地址 printf("%d", *pint); }
1五、語言擴展到通用屬性。
編譯器廠商或組織設計出了一系列的語言擴展(language extension)來擴展語法。這些擴展語法並不存在於C++標準中。
好比 g++,使用 __attribute__ 來聲明屬性。
__attribute__((attribute-list))
下面的 const屬性告訴編譯器:本函數返回值只依賴於輸入,不會改變任何函數外的數據,所以沒有任何反作用。從而編譯器能夠將其優化爲常量。
exter int area(int n) __attribute__((const)) int main() { int i; int areas = 0; for (i = 0; i < 10; i++){ areas += area(3)*i; } }
windows平臺上,使用 __declspec 來定義屬性。如字節對齊:
__declspec(extened-decl-modifier)
__declspec(align(32)) struct Struct32{ int i; double d; };
1六、C++11 的通用屬性。
C++通用屬性能夠做用於類型、變量、名稱、代碼塊等。
1)做用於聲明的通用屬性,便可以寫在聲明的前面,也能夠寫在聲明的標識符以後。
2)做用於整個語句的通用屬性,應該寫在語句的起始處。
// 狀況一,attr一、attr2 均做用於函數 func [[attr1]] void func [[attr2]] (); // 狀況一,同上 [[attr1]] int arra [[attr2]] [10];
[[attr1]] int func([[attr2]] int i, [[attr3]] int j) { [[attr4]] return i+j; }
C++11 中,只預約義了兩個通用屬性 [[noreturn]] 、[[carries_dependency]]。
1七、預約義的通用屬性。
[[noreturn]] 用於標識不會返回的函數。用於標識那些不會將控制流程返回的函數。
void DoSomething1(); void DoSomething2(); [[noreturn]] void Throwaway(){ throw "expection"; // 控制流跳轉到異常處理 } void Func() { DoSomething1(); ThrowAway(); DoSomething2(); // 該處不可達。 }
若是無心中寫了 [[noreturn]],但寫返回了控制流程,則會發生段錯誤。
[[carries_dependency]] 告訴編譯器調用的函數與當前代碼無依賴,以免插入內存柵欄 sync。 截至 2013,漢網有編譯器支持 [[carries_dependency]]屬性。
1八、字符集、編碼、Unicode
ASCII使用7個二進制位進行標識 ,總共能夠標識 128種不一樣的字符 。
比較覺的基於 Unicode字符集的編碼方式有UTF-八、UTF-16及UTF-32。通常人經常把UTF-16和Unicode混爲一談。
UTF-8,採用1-6字節的變長編碼。英文一般使用1字節,且與ASCII兼容。而中文經常使用3字節表示。UTF-8因爲節約存儲空間,所以使用得比較普遍。
Windows內部採用了UTF-16,而 MacOS、Linux 等則採用了 UTF-8 編碼方式。
GB2312先於Unicode出現。早在20世紀80年代,做爲國家標準被頒佈使用。2個字節一箇中文字符。在大陸、新加坡有普遍使用。
BIG5用於繁體中文。2字節表示產一個字符。在香港、臺灣、澳門有着普遍的使用。
1九、C++11 中Unicode的支持。
C++98 爲了支持寬字符,添加了 wchar_t。標準規定,wchar_t的寬度由編譯器實現決定。windows上 wchar_t被實現爲16位寬,linux上被實現爲32位。如此導wchar_t的代碼一般不可移植。
C++11引入了兩種新的類型:
1)char16_t
2)char32_t
C++11 還定義了常量字符串前綴:
1)u8,表示 UTF-8
2)u,UTF-16
3)U,UTF-32
wchar_t 的前綴爲 L 。加上普通字符串,一塊兒是5種表達。
編譯器會自動將其鏈接起來,好比「a」"b" 會變爲"ab"。u"a""b",會成爲 "u""ab"。
C++11 中還規定了簡明的 Unicode字符引用方式,如 '\u4F60' 表示UTF-16編碼的字符,是「你」。'\U'後跟8個十六進度,表示UTF-32。
某些系統只能輸出 utf-8。
C++11 爲 char16_t、char32_t 分別配備了 u16string、u32string。
utf8_t 能夠用來引用utf-8字符串。
20、關於 Unicode 庫的支持
C11中,新增了一些編碼轉換函數。下面代碼中,mb是multi-byte的縮寫。c1六、c32是char1六、char32的縮寫。rt是convert的縮寫。mbstate_t是用於返回轉換中的狀態信息。
上述代碼的使用須要 #include <cuchar>。
C++中引入 了local機制。一個locale有不少facet/interface。codecvt是其中一個facet,提供當前 locale下多字符編碼到多種 Unicode字符編碼轉換。C++標準規定,一共要實現4種這樣的codecvt facet。
一個 locale 並不必定支持全部的 codecvt。能夠經過 has_facet 來查詢 。
locale lc("en_US.UTF-8"); bool res = has_facet<codecvt<wchar_t,char,mbstate_t>>(lc);
2一、原生字符串
R"()",原生字符串中轉義字符失效。
22