第1章:C++泛型技術基礎:模板——《C++泛型:STL原理和應用》讀書筆記整理

第1章:C++泛型技術基礎:模板

1.2 關於模板參數
1.2.1 模板參數類型
  1. 類型參數
      typename聲明的參數都屬於類型參數,它的實參必須爲系統內置或者用戶自定義的數據類型,包括類模板實體,由類模板產生的類模板實體,本質上就是類。ios

  2. 非類型參數
      C++容許人們在模板參數列表中像函數參數列表中那樣定義普通變量或者對象。定義的普通變量不能被修改,由於模板參數是在預編譯期間進行傳遞而且被編譯的。僅支持能夠轉換爲Int類型的變量(double都不行!)、枚舉、指針、引用。c++

    template<typename T, int b>
  3. 模板定義型參數
      C++也容許以類模板的定義做爲類模板參數,之因此須要這種參數,其目的就是除了強調這個參數的實參必須爲類模板外,還強調這個類模板所具備的參數個數。算法

    //其中T就是一個單參數類模板
     template<template<typename S>class T>
1.2.2 模板形參和實參的結合
  1. 函數模板實參的隱式提供
      實參的隱式提供須要提供返回值的類型,或者可使用auto+decltype大法推導返回值的類型。
  2. 指針實參
      指針實際上是類型參數的一種,使用4字節的存儲空間。
  3. 修飾符const和&的使用
      修飾符const和&只是對模板參數的修飾,對模板參數類型無太大影響
1.3 特化模板和模板具現規則
1.3.1 特化模板(特例化模板)
  1. 函數模板中的特化模板
      沒啥好說的就是普通的特化。不過模板函數也是能夠偏特化的,好比一個雙參數函數,是能夠特化第一個參數的。
  2. 類模板的特化和偏特化
      偏特化就是指特化一個模板參數。
  3. 模板的具現
      在源文件中,咱們可使用多種方式編寫一個泛型的程序功能模塊,他多是特化模板、偏特化模板以及全特化模板,或者這幾種泛型模塊共存。顯然編譯器須要一個模板具現的規則,優先生成哪一個模板的實體。編譯器對於一個模塊調用的具現優先規則應爲:
特化模板 > 偏特化模板 > 普通模板 > 系統
1.4 右值引用與模板
1.4.1 右值引用

  左值值既能夠出如今賦值運算符的左邊和右邊,右值只能夠出如今賦值運算法的右邊。左值之因此能夠出如今賦值運算符的左邊,就是由於這種表達式表明一塊存儲空間,能夠接受並保存數據。程序能夠經過其變量名獲取地址,並用這個地址訪問數據。右值僅能表明數據。右值表達式要麼是數據自己,要麼是一個能得出結果的運算表達式,儘管它也佔據必定的存儲空間,可是由於它沒有名字,也不能從其表達式中提取這個空間的地址。所以這種表達式只能出如今賦值運算符的右邊,並且僅能表明生命期與其所在語句相同的臨時對象
  關於引用,在C++11以前,左值能夠定義兩種引用:左值常引用和左值引用。對於右值,C++11以前僅定義了一種常量引用。對右值進行常量引用能夠延長右值的生命期,從而使後續程序能夠利用它的信息,遺憾的就是它是常量,不能知足程序更多的要求。
  爲了能充分利用臨時對象,C++11標準推出了一種新的數據類型——右值的很是量引用,簡稱右值引用。ide

T&& name = rvalue;
1.4.2 右值引用應用1——轉移語義

深拷貝與淺拷貝:函數

#include <iostream> 
using namespace std;
class Student {
private:
    int num;
    char *name;
public:
    Student();
    ~Student();
};
Student::Student(){
    name = new char(20);
    cout << "Student" << endl;
}
Student::~Student(){
    cout << "~Student " << (int)name << endl;
    delete name;
    name = NULL;
} 
int main()
{
    Student s1;
    // 使用了默認拷貝函數,默認拷貝函數進行的是淺拷貝
    // 所以在析構的時候會對同一個內存空間釋放兩次,形成內存泄漏。
    Student s2(s1);
    return 0;
}

  淺拷貝優勢是速度快,節省資源,缺點是共享了資源,容易引發內存泄漏。深拷貝不會引發泄漏,可是每次拷貝都消耗大量資源。優化

轉移語義:spa

//關閉RVO,return value optimistic,返回值優化
//不關閉在返回右值的時候會跳過移動構造函數,直接構造對象。
g++ -fno-elide-constructors e1_b.cpp -o e1_b && ./e1_b

Foo fuct(){
    Foo foof(100);  //產生局部變量,生命週期只有在fuct()函數中,在函數返回的時候會被析構。
    return foof;    //這裏實際上是調用了Foo類的拷貝函數,拷貝了一份做爲返回值。
}

fuct();         
        Foo(Foo&& r)    //調用拷貝函數,返回一份拷貝
        ~Foo()          //函數中的局部變量生命週期結束
        ~Foo()          //函數返回值的生命週期結束,在主函數return的時候

//爲何會調用了一次析構函數呢?
//由於foo1的生命週期是整個主函數,少的那一次在主函數return的時候會被調用
//不是初始化!沒有調用構造函數!聲明瞭一個常引用接受fuct()的返回值。
const Foo& foo1 = fuct();   
        Foo(Foo&& r)    //拷貝局部變量做爲返回值
        ~Foo()          //局部變量聲明週期結束

//與上相同,只是一個是常引用,另外一個是很是量引用
Foo&& foo2 = fuct();
        Foo(Foo&& r)
        ~Foo()

//隱式調用構造函數和顯式調用構造函數!
Foo foo3(fuct());
Foo foo3 = Foo(fuct());
        Foo(Foo&& r)    //拷貝做爲返回
        ~Foo()          //析構局部變量
        Foo(Foo&& r)    //調用拷貝構造函數,建立對象foo3,
        ~Foo()          //返回值聲明週期結束,調用析構函數

return 0!

  在某些狀況下,被拷貝的對象是右值,意味着其生命週期即將結束,此時若是咱們再去消耗資源開闢新空間就顯得浪費了。由於被拷貝對象即將」死亡「,咱們不妨借用一下這個右值的內存空間!這就是語義轉移,即便用淺拷貝共享內存空間後,將」強迫「原對象放棄資源控制權(指針爲空),避免內存空間被釋放。經過這種方式,原對象(右值)的內存空間,被咱們轉移(竊取)出來,用於新對象,經過這種方式,咱們極大地節省了開銷。設計

1.4.3 右值引用應用2——轉移函數move()

  看到了右值引用的好處,左值引用也想利益共沾。可是右值引用,必須參數要是右值才能完成語義轉移,由於左值在邏輯上是不容許被竊取內存空間的。指針

T&& std::move(T&);
void swap(T& a, T& b){
    T tmp = std::move(a);
    a = std::move(b);
    b = std::move(tmp);
}
1.4.4 右值引用應用3——參數完美轉發模板

  在程序設計實踐中,函數模板常常須要調用一些外部函數實現它的功能,這時候就須要模板爲這些函數傳遞參數,人們習慣上把模板的這種行爲叫作參數轉發。對於模板來講,它的任務就是參數的忠實轉發,一不能改變參數特性,二不能產生額外開銷。若是模板能把全部數據類型都按照上述要求進行轉發,那麼這個模板就是一個完美參數轉發者。code

//方案一:
void Func(int);     //目標函數
template<typename T>
Tmp(T);             //轉發模板
//結果:這種方式在Tmp(10),參數爲右值的時候,在經模板轉發後變成了左值

//方案二:
void Func(int v);
template<typename T>
Tmp(T&);
//結果:沒法轉發右值,Tmp(100)報錯,由於模板爲左值引用
//改進,Tmp(const T&),此時模板轉發後參數變爲常引用,可能不符合某些函數要求

//方案三:
void Func(int& v);
template<typename T>
Tmp(T&& a){
    Func(a);
}
//結果:C++11前沒法經過,由於C++11前尚未右值引用T&&,以及對多個引用符&連用的數據類型進行推導的功能
/*      多重引用推導表
實參類型    模板參數    推導類型
int&        T&          int&
int&&       T&          int&
int&        T&&         int&
int&&       T&&         int&&

lvalue      T&&         T&      左值 + T&& = T&
rvalue      T&&         T       右值 + T&& = T(左值)
*/
//在這種推導狀況下,若是Tmp的參數爲右值,在轉發後參數類型就會被推導爲左值,這與咱們的預期不一樣。
//對此咱們在Tmp函數內對參數進行強制的類型轉換
Tmp(T&& a){
    Func(static_cast<T&&>(a));
}
//由於左值+T&& = T&&, T& + T&& = T&, 實現了參數的完美轉發。
//爲了區別於move()和static_cast,並使之更具備語義性,C++11將static_cast()封裝在函數模板std:forward()。
Tmp(T&& a){
    func(std::forward<T>(a));
}
相關文章
相關標籤/搜索