12、 C++特性之 雜合

static_assert和 type traits

static_assert提供一個編譯時的斷言檢查。若是斷言爲真,什麼也不會發生。若是斷言爲假,編譯器會打印一個特殊的錯誤信息。程序員

  1. template <typename T, size_t Size> 
  2. class Vector 
  3.    static_assert(Size < 3, "Size is too small"); 
  4.    T _points[Size]; 
  5. }; 
  6.   
  7. int main() 
  8.    Vector<int, 16> a1; 
  9.    Vector<double, 2> a2; 
  10.    return 0; 
  11. }
  1. error C2338: Size is too small 
  2. see reference to class template instantiation 'Vector<T,Size>' being compiled 
  3.    with 
  4.    [ 
  5.       T=double, 
  6.       Size=2 
  7.    ] 

 

static_assert和type traits一塊兒使用能發揮更大的威力。type traits是一些class,在編譯時提供關於類型的信息。在頭文件<type_traits>中能夠找到它們。這個頭文件中有好幾種 class: helper class,用來產生編譯時常量。type traits class,用來在編譯時獲取類型信息,還有就是type transformation class,他們能夠將已存在的類型變換爲新的類型。算法

下面這段代碼本來指望只作用於整數類型。數組

  1. template <typename T1, typename T2> 
  2. auto add(T1 t1, T2 t2) -> decltype(t1 + t2) 
  3. return t1 + t2; 

可是若是有人寫出以下代碼,編譯器並不會報錯安全

  1. std::cout << add(1, 3.14) << std::endl; 
  2. std::cout << add("one", 2) << std::endl; 

程序會打印出4.14和」e」。可是若是咱們加上編譯時斷言,那麼以上兩行將產生編譯錯誤。多線程

  1. template <typename T1, typename T2> 
  2. auto add(T1 t1, T2 t2) -> decltype(t1 + t2) 
  3.    static_assert(std::is_integral<T1>::value, "Type T1 must be integral"); 
  4.    static_assert(std::is_integral<T2>::value, "Type T2 must be integral"); 
  5.   
  6.    return t1 + t2; 
  7. }
  1. error C2338: Type T2 must be integral 
  2. see reference to function template instantiation 'T2 add<int,double>(T1,T2)' being compiled 
  3.    with 
  4.    [ 
  5.       T2=double, 
  6.       T1=int 
  7.    ] 
  8. error C2338: Type T1 must be integral 
  9. see reference to function template instantiation 'T1 add<const char*,int>(T1,T2)' being compiled 
  10.    with 
  11.    [ 
  12.       T1=const char *, 
  13.       T2=int 
  14.    ] 

 

 

 

Move semantics (Move語義)async

這是C++11中所涵蓋的另外一個重要話題。就這個話題能夠寫出一系列文章,僅用一個段落來講明顯然是不夠的。所以在這裏我不會過多的深刻細節,若是你還不是很熟悉這個話題,我鼓勵你去閱讀更多地資料。函數

C++11加入了右值引用(rvalue reference)的概念(用&&標識),用來區分對左值和右值的引用。左值就是一個有名字的對象,而右值則是一個無名對象(臨時對 象)。move語義容許修改右值(之前右值被看做是不可修改的,等同於const T&類型)。學習

C++的class或者struct之前都有一些隱含的成員函數:默認構造函數(僅當沒有顯示定義任何其餘構造函數時才存在),拷貝構造函數,析構 函數還有拷貝賦值操做符。拷貝構造函數和拷貝賦值操做符提供bit-wise的拷貝(淺拷貝),也就是逐個bit拷貝對象。也就是說,若是你有一個類包含 指向其餘對象的指針,拷貝時只會拷貝指針的值而不會管指向的對象。在某些狀況下這種作法是沒問題的,但在不少狀況下,實際上你須要的是深拷貝,也就是說你 但願拷貝指針所指向的對象。而不是拷貝指針的值。這種狀況下,你須要顯示地提供拷貝構造函數與拷貝賦值操做符來進行深拷貝。this

若是你用來初始化或拷貝的源對象是個右值(臨時對象)會怎麼樣呢?你仍然須要拷貝它的值,但隨後很快右值就會被釋放。這意味着產生了額外的操做開銷,包括本來並不須要的空間分配以及內存拷貝。spa

如今說說move constructor和move assignment operator。這兩個函數接收T&&類型的參數,也就是一個右值。在這種狀況下,它們能夠修改右值對象,例如「偷走」它們內部指針所 指向的對象。舉個例子,一個容器的實現(例如vector或者queue)可能包含一個指向元素數組的指針。當用一個臨時對象初始化一個對象時,咱們不需 要分配另外一個數組,從臨時對象中把值複製過來,而後在臨時對象析構時釋放它的內存。咱們只須要將指向數組內存的指針值複製過來,由此節約了一次內存分配, 一次元數組的複製以及後來的內存釋放。

如下代碼實現了一個簡易的buffer。這個buffer有一個成員記錄buffer名稱(爲了便於如下的說明),一個指針(封裝在unique_ptr中)指向元素爲T類型的數組,還有一個記錄數組長度的變量。

 

  1. template <typename T> 
  2. class Buffer 
  3.    std::string          _name; 
  4.    size_t               _size; 
  5.    std::unique_ptr<T[]> _buffer; 
  6.   
  7. public: 
  8.    // default constructor 
  9.    Buffer(): 
  10.       _size(16), 
  11.       _buffer(new T[16]) 
  12.    {} 
  13.   
  14.    // constructor 
  15.    Buffer(const std::string& name, size_t size): 
  16.       _name(name), 
  17.       _size(size), 
  18.       _buffer(new T[size]) 
  19.    {} 
  20.   
  21.    // copy constructor 
  22.    Buffer(const Buffer& copy): 
  23.       _name(copy._name), 
  24.       _size(copy._size), 
  25.       _buffer(new T[copy._size]) 
  26.    { 
  27.       T* source = copy._buffer.get(); 
  28.       T* dest = _buffer.get(); 
  29.       std::copy(source, source + copy._size, dest); 
  30.    } 
  31.   
  32.    // copy assignment operator 
  33.    Buffer& operator=(const Buffer& copy) 
  34.    { 
  35.       if(this != ©) 
  36.       { 
  37.          _name = copy._name; 
  38.   
  39.          if(_size != copy._size) 
  40.          { 
  41.             _buffer = nullptr; 
  42.             _size = copy._size; 
  43.             _buffer = _size > 0 > new T[_size] : nullptr; 
  44.          } 
  45.   
  46.          T* source = copy._buffer.get(); 
  47.          T* dest = _buffer.get(); 
  48.          std::copy(source, source + copy._size, dest); 
  49.       } 
  50.   
  51.       return *this; 
  52.    } 
  53.   
  54.    // move constructor 
  55.    Buffer(Buffer&& temp): 
  56.       _name(std::move(temp._name)), 
  57.       _size(temp._size), 
  58.       _buffer(std::move(temp._buffer)) 
  59.    { 
  60.       temp._buffer = nullptr; 
  61.       temp._size = 0; 
  62.    } 
  63.   
  64.    // move assignment operator 
  65.    Buffer& operator=(Buffer&& temp) 
  66.    { 
  67.       assert(this != &temp); // assert if this is not a temporary 
  68.   
  69.       _buffer = nullptr; 
  70.       _size = temp._size; 
  71.       _buffer = std::move(temp._buffer); 
  72.   
  73.       _name = std::move(temp._name); 
  74.   
  75.       temp._buffer = nullptr; 
  76.       temp._size = 0; 
  77.   
  78.       return *this; 
  79.    } 
  80. }; 
  81.   
  82. template <typename T> 
  83. Buffer<T> getBuffer(const std::string& name) 
  84.    Buffer<T> b(name, 128); 
  85.    return b; 
  86. int main() 
  87.    Buffer<int> b1; 
  88.    Buffer<int> b2("buf2", 64); 
  89.    Buffer<int> b3 = b2; 
  90.    Buffer<int> b4 = getBuffer<int>("buf4"); 
  91.    b1 = getBuffer<int>("buf5"); 
  92.    return 0; 

默認的copy constructor以及copy assignment operator你們應該很熟悉了。C++11中新增的是move constructor以及move assignment operator,這兩個函數根據上文所描述的move語義實現。若是你運行這段代碼,你就會發現b4構造時,move constructor會被調用。一樣,對b1賦值時,move assignment operator會被調用。緣由就在於getBuffer()的返回值是一個臨時對象——也就是右值。

你也許注意到了,move constuctor中當咱們初始化變量name和指向buffer的指針時,咱們使用了std::move。name其實是一個 string,std::string實現了move語義。std::unique_ptr也同樣。可是若是咱們寫_name(temp._name), 那麼copy constructor將會被調用。不過對於_buffer來講不能這麼寫,由於std::unique_ptr沒有copy constructor。但爲何std::string的move constructor此時沒有被調到呢?這是由於雖然咱們使用一個右值調用了Buffer的move constructor,但在這個構造函數內,它其實是個左值。爲何?由於它是有名字的——「temp」。一個有名字的對象就是左值。爲了再把它變爲 右值(以便調用move constructor)必須使用std::move。這個函數僅僅是把一個左值引用變爲一個右值引用。

更新:雖然這個例子是爲了說明如何實現move constructor以及move assignment operator,但具體的實現方式並非惟一的。在本文的回覆中Member 7805758同窗提供了另外一種可能的實現。爲了方便查看,我把它也列在下面:

  1. template <typename T> 
  2. class Buffer 
  3.    std::string          _name; 
  4.    size_t               _size; 
  5.    std::unique_ptr<T[]> _buffer; 
  6.   
  7. public: 
  8.    // constructor 
  9.    Buffer(const std::string& name = "", size_t size = 16): 
  10.       _name(name), 
  11.       _size(size), 
  12.       _buffer(size? new T[size] : nullptr) 
  13.    {} 
  14.   
  15.    // copy constructor 
  16.    Buffer(const Buffer& copy): 
  17.       _name(copy._name), 
  18.       _size(copy._size), 
  19.       _buffer(copy._size? new T[copy._size] : nullptr) 
  20.    { 
  21.       T* source = copy._buffer.get(); 
  22.       T* dest = _buffer.get(); 
  23.       std::copy(source, source + copy._size, dest); 
  24.    } 
  25.   
  26.    // copy assignment operator 
  27.    Buffer& operator=(Buffer copy) 
  28.    { 
  29.        swap(*this, copy); 
  30.        return *this; 
  31.    } 
  32.   
  33.    // move constructor 
  34.    Buffer(Buffer&& temp):Buffer() 
  35.    { 
  36.       swap(*this, temp); 
  37.    } 
  38.   
  39.    friend void swap(Buffer& first, Buffer& second) noexcept 
  40.    { 
  41.        using std::swap; 
  42.        swap(first._name  , second._name); 
  43.        swap(first._size  , second._size); 
  44.        swap(first._buffer, second._buffer); 
  45.    } 
  46. }; 

結論

關於C++11還有不少要說的。本文只是各類入門介紹中的一個。本文展現了一系列C++開發者應當使用的核心語言特性與標準庫函數。然而我建議你能更加深刻地學習,至少也要再看看本文所介紹的特性中的部分。

 

 

 

Deleted和Defaulted函數

一個表單中的函數:

  1. struct A  
  2. {  
  3.  A()=default; //C++11  
  4.  virtual ~A()=default; //C++11  
  5. };  

被稱爲一個defaulted函數,「=default;」告訴編譯器爲函數生成默認的實現。Defaulted函數有兩個好處:比手工實現更高效,讓程序員擺脫了手工定義這些函數的苦差事。

與defaulted函數相反的是deleted函數:

  1. int func()=delete;

Deleted函數對防止對象複製頗有用,回想一下C++自動爲類聲明一個副本構造函數和一個賦值操做符,要禁用複製,聲明這兩個特殊的成員函數=delete便可:

  1. struct NoCopy  
  2. {  
  3.     NoCopy & operator =( const NoCopy & ) = delete;  
  4.     NoCopy ( const NoCopy & ) = delete;  
  5. };  
  6. NoCopy a;  
  7. NoCopy b(a); //compilation error, copy ctor is deleted

 

 

委託構造函數

在C++11中,構造函數能夠調用相同類中的其它構造函數:

  1. class M //C++11 delegating constructors  
  2. {  
  3.  int x, y;  
  4.  char *p;  
  5. public:  
  6.  M(int v) : x(v), y(0),  p(new char [MAX])  {} //#1 target  
  7.  M(): M(0) {cout<<"delegating ctor"<  

構造函數#2,委託構造函數,調用目標構造函數#1。

 

 

 

線程庫

站在程序員的角度來看,C++11最重要的新功能毫無疑問是並行操做,C++11擁有一個表明執行線程的線程類,在並行環境中用於同步,async()函數模板啓動並行任務,爲線程獨特的數據聲明thread_local存儲類型。若是你想找C++11線程庫的快速教程,請閱讀Anthony William的「C++0x中更簡單的多線程」。

 

 

 

新的算法

C++11標準庫定義了新的算法模仿all_of(),any_of()和none_of()操做,下面列出適用於ispositive()到(first, first+n)範圍,且使用all_of(), any_of() and none_of() 檢查範圍的屬性的謂詞:

  1. #include <algorithm>  
  2. //C++11 code  
  3. //are all of the elements positive?  
  4. all_of(first, first+n, ispositive()); //false  
  5. //is there at least one positive element?  
  6. any_of(first, first+n, ispositive());//true  
  7. // are none of the elements positive?  
  8. none_of(first, first+n, ispositive()); //false  

一種新型copy_n算法也可用了,使用copy_n()函數,複製一個包含5個元素的數組到另外一個數組的代碼以下:

  1. #include  
  2. int source[5]={0,12,34,50,80};  
  3. int target[5];  
  4. //copy 5 elements from source to target  
  5. copy_n(source,5,target);  

算法iota()建立了一個值順序遞增的範圍,好像分配一個初始值給*first,而後使用前綴++使值遞增,在下面的代碼中,iota()分配連續值{10,11,12,13,14}給數組arr,並將{‘a’,’b’,’c’}分配給char數組c。

  1. include <numeric>  
  2. int a[5]={0};  
  3. char c[3]={0};  
  4. iota(a, a+5, 10); //changes a to {10,11,12,13,14}  
  5. iota(c, c+3, 'a'); //{'a','b','c'}  

C++11仍然缺少一些有用的庫,如XML API,套接字,GUI,反射以及前面提到的一個合適的自動垃圾回收器,但C++11的確也帶來了許多新特性,讓C++變得更加安全,高效,易學易用。

若是C++11的變化對你來講太大的話,也不要驚慌,多花些時間逐漸消化這一切,當你徹底吸取了C++11的變化後,你可能就會贊成Stroustrup的說法:C++11感受就像一個新語言,一個更好的新語言。

 

 

 

變長參數的模板

咱們在C++中都用過pair,pair可使用make_pair構造,構造一個包含兩種不一樣類型的數據的容器。好比,以下代碼:

auto p = make_pair(1, "C++ 11");


因爲在C++11中引入了變長參數模板,因此發明了新的數據類型:tuple,tuple是一個N元組,能夠傳入1個, 2個甚至多個不一樣類型的數據

 

auto t1 = make_tuple(1, 2.0, "C++ 11");
auto t2 = make_tuple(1, 2.0, "C++ 11", {1, 0, 2});

這樣就避免了從前的pair中嵌套pair的醜陋作法,使得代碼更加整潔

另外一個常常見到的例子是Print函數,在C語言中printf能夠傳入多個參數,在C++11中,咱們能夠用變長參數模板實現更簡潔的Print

 

template<typename head, typename... tail>
void Print(Head head, typename... tail) {
    cout<< head <<endl;
    Print(tail...);
}
相關文章
相關標籤/搜索