深刻理解C++11【5】

深刻理解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;
}
View Code

  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;
}
View Code

 

  順序一致、鬆散、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

相關文章
相關標籤/搜索