C++模板——读书总结篇

函数模板

定义

  • 例子一

    template<typename T>
    void Func() {
    	cout << "hello, world" << endl;
    }
    
  • 例子二

    template <typename T, template<typename,typename> class A, size_t N>
    void Func(T input) {
    	T a = input;
    	A<int , double> a;
    	int array[N] = {};
    }
    

实例化

  • 隐式实例化

    1. 编译器使用函数实参来推断模板实参进行实例化。
    2. 对于返回值类型为模板参数的情况,编译器无法推断,必须显示指定。
    3. 当我们使用一个函数模板初始化一个函数指针时,编译器会使用函数指针的类型来推断模板实参的。如果不能唯一实例化(编译器推断不出来了),该操作就会失败。
  • 显示实例化
    当隐式无法实例化时,可以显示实例化。 例如:

    // 函数模板的声明与定义
    template<typename RetType, typename T>
    RetType Func(T input) {
    	RetType a = 1;
    	return a;
    }
    
    // 函数模板的实例化
    double a = 100.4;
    Func<int>(a);
    

类模板

模板的定义

  1. 举例一: 普通的定义

    template<typename T>
    class Base {
    };
    
  2. 举例二:使用模板类作为类型参数

    template<typename T>
    class Base {
    };
    
    // 模板类作为类型形参数。
    template<typename T, template<typename M> class Base>
    class Other {
    	
    }
    
  3. 模板类的成员函数:对于类模板的成员函数,即可以在类内定义(默认为inline),也可以在类外定义(需要使用template关键字开始)。

    // 类内定义
    template<typename T>
    class A {
    public:
    	void Print() {
    		cout << "hello, world" << endl;
    	}
    };
    
    // 类外定义
    template<typename T>
    class A {
    public:
        void Print();
    }
    
    template<typename T>
    void A<T>::Print() {
        cout << "hello, owrld" << endl;
    }
    
  4. 类模板的类型名的简写: 在类模板自己的作用域时,可以直接使用类模板名,而不需要类模板名<类型参数> , 就是相当于简化了。例如:

    template<typename T>
    class A {
    public:
    A<T>& GetInstance() {
    	A* instance = new A;  // A<T> 可以简写为A.
    	return *instance;
    }
    };
    

类模板的实例化

  1. 默认情况下,一个类模板的成员函数只有在使用时,它才被实例化。所以呢, 有一些成员函数无法实例化时, 只要不使用它,就没有问题。
  2. 使用类模板时, 必须使用在<>中加上类型参数 进行显式实例化。

类模板与友元

  1. 当一个类包含一个友元时,类与友元各自是否为模板是相互无关的。如果一个类包含了一个非模板的友元,则友元被授权可以访问所有模板实例。 如果友元自己是模板,类可以授权给所有友元模板实例。
  2. 为了引用一个模板的特定实例,必须先声明模板自身。
  3. 在新标准中,可以将模板类型参数声明为友元。
    template<typename type> 
    class Bar { friend Type;};
    

模板类的别名

  1. typedef 不允许为模板定义一个类型别名,因为模板不是一种类型。

  2. 使用using 可以为模板定义一个别名,例如:

    template<typename T, typename M> 
    using twin = pair<T, M>;
    
    template<typename T>
    using ttt = pair<T, int>;
    
    twin<int, char> a;
    ttt<double> b;
    

模板类内的类型成员

  1. 在模板内部可以定义一种类型,但是在使用的时候存在问题,编译器无法区别使用的类型还是类内的static 变量?例如 : T::new_type, 这个new_type到底是变量还是类型呢?
  2. 默认情况下, c++语言假定通过作用域运算符访问的名字不是类型
  3. 当我们使用类型时, 使用typename 来标识它,例如: typename T::new_type a(定义变量a)。

成员函数模板

  1. 一个类(无论是否为模板类),都只可以是模板的成员函数,这种成员叫作成员模板。

  2. 成员模板不可以是虚函数。

  3. 普通类的成员模板,即普通类的成员函数为模板函数,性质与函数模板相同。

  4. 类模板的成员模板:
    a. 类与成员模板,它们有各自的独立的模板参数。
    b. 当我们在类模板之外定义一个成员模板时,必须同时为类模板与成员模板提供参数列表。
    c. 类模板的参数列表在前,成员模板的模板参数在后。

    // 类模板声明
    template<typename T>
    class A {
    public:
    	template<typename M>
    	void Print();
    };
    
    // 成员函数的定义
    template<typename T>
    template<typename M>
    void A<T>::Print() {
    	cout << "hello" << endl;
    }
    
    // 使用
    A<int> a;
    a.Print<double>();
    

别名模板

template<typename T>
using myVec = vector<T, allocator<T>>;

// 使用举例
template<template<typename T> class Continaer>
class Test {
};

// Test<vector> test1;  // 编译报错
Test<myVec> test2;   // OK

模板的参数

  1. 对于模板参数,即可以为类型,也可以为非类型参数,也可以为模板类。

  2. 当模板的参数为非类型参数时,
    a. 它为一个值,而非类型,编译期可以确认的值。
    b. 它只可以为整型、可以为对象或函数类型的指针或左值引用。(对象必须有静态生命周期,就是全局变量了。 如果是static 变量,需要是const的,原因是非const的static变量的地址编译器可能无法确定,因为的是文件作用域,我猜的)

    // 类型参数
    template<typename T>
    void Print();
    
    // 非类型参数
    template<size_t M>
    void Print();
    
    // 模板类参数
    template <template<typename> class A>
    void Print();
    
    // 指针或引用
    template<int* p>
    void Print() {
        cout << 指针 << endl;
    }
    
    template<int& v>
    void Print() {
    cout << 引用 << endl;
    }
    
    int a = 10;
    int& b = a;
    Print<&a>();
    Print<b>();
    
  3. 默认参数
    a. 例如:template<typename T = int, typename Y = double>
    b. 对于模板参数,只有当它右侧的所有参数都有默认实参时,它才可以有默认实参。

模板的实参的推断

  1. 指的是函数模板中根据实参类型进行推断形参。

  2. 对于函数模板来说,只有两种类型转换(隐式转换).除此之外,算术类型的转换、派生类到基类的转换、用户自定义的转换等都是不允许的
    a). const 转换,一个非const 对象的引用或指针的实参可以传递给一个const的引用或指针的形参。
    b). 数组与函数指针转换:如果函数不是引用类型(当是引用类型时,数组的大小不一样,类型不同)时,可以对数组或函数类型的实参应用正常的指针转换。即数组转换为指针,函数转换为函数指针。

  3. 在函数模板中,如果使用到普通类型定义的参数, 它们是可以正常转换的。 只有 模板的类型参数不可以正常转换。

  4. 正常的类型转换也可以应用于显示指定的实参的。

  5. 当我们使用一个函数模板初始化一个函数指针时,编译器会使用函数指针的类型来推断模板实参的。如果不能唯一实例化(编译器推断不出来了),该操作就会失败。

  6. 重量级知识点:引用折叠与右值引用参数:
    a): 当模板参数为左值引用时,只能传递给它的一个左值的实参。
    b): 当模板参数为const 左值引用时,传递给它的一个左值的实参/临时对象、字面值等
    c): 神奇到来:当模板函数参数是一个右值引用时,传递给它的实参可以是任意类型(也就是说左值也可以),原因就是因为引用折叠。
    d): 例外规则一: 当我们将一个左值传给模板函数的右值引用参数(T&&)时, 编译器推断模板类型参数为的左值引用类型,例如为int类型时,推断T为int&.
    e): 例外规则二:如果我们间接创建了一个引用的引用,则这些引用形成了引用折叠。正常情况下,不能直接创建引用的引用,但是可以间接创建。大部分情况下,引用的引用会折叠为普通的左值引用(T& &、T& &&、 T&& &),右值引用的右值引用,分折叠成右值引用。
    f): 通过两个规则,得出一个结论:如果一个函数模板参数为右值引用,则可以传递给它任意类型的实参

  7. 引用折叠,引出的std::move的实现:

    template <typename T>
    typename remove_reference<T>::type&& move(T&& t) {
    return static_cast<typename remove_reference<T>::type&&>(t);
    }
    

控制模板的实例化

  1. 编译器对模板的处理:当编译器遇到模板定义时,并不生成代码,而是当实例化模板时,才会生成代码。也就是说,使用的时候才生成代码。

  2. 通常情况下,当模板被使用时,才会进行实例化。 这意味着,相同的实例可能出现在多个对象文件中。在大的系统中,多个文件实例化相同模板的开销会很大,后处理时还要相办法只保留一份。

  3. 在新的标准中,可以通过显示实例化来控制实例化的过程。

  4. 实例化的声明,格式为: extern template declaration, 其中declaration 是类或函数声明,并且其中模板参数都已经被替换为模板的实参。在实例化的声明与定义之前,都应该已经定义好了模板类或者模板函数,否则编译报错

    // 实例化的声明
    extern template class Blob<int>;
    extern template Func(int a);
    
    // 模板类或模板函数的声明,注意区分它们。
    template< typname T>
    class A;
    template<typename T>
    void Func();
    
  5. 实例化定义, 格式为:template declaration

    // 实例化的定义
    template class Blob<int>;
    template Func(int a);
    
  6. 额外知识点:
    a). 当编译器遇到extern时,不会在本文件中生成实例化的代码。
    b). 将一个实例化的声明为extern就表示了在程序的其它位置有该实例化(不包含extern)
    c). 一个类模板的实例化定义会实例化所有的成员函数。 这与处理类模板的普通实例化是不相同的(通常情况下,使用到的时候才进行实例化)

模板的特例化

  1. 特例化, 就是我们人工地为编译器针对模板做了推导工作,遇到相同参数类型时,请使用人工给的模板样例,编译器别自己推断了。

  2. 特例化与实例化是不同的。 实例化,编译器会生成实例代码的,比如一个函数模板,实例化时,会生成对应的函数。特例化只是给了一个人工的模板,编译器实例化时,请优先使用我给的特例化模板。

  3. 全特例化:把所有的形参都进行特例化.

    template <typename T1, typename T2>
    void Func() {
    	cout << "hello" << endl;
    }
    
    template<>
    void Func<int ,double>() {
    	cout << "wow" << endl;
    }
    
  4. 部分特例化:
    a). 类模板可以部分特例化,函数模板不可以部分特例化。
    b). 部分特例化分两种:特例化一部分参数和特例化参数的一部分。

    // 特例化一部分参数
    template <typename T1, typename T2>
    class A {
    public:
        A() { cout << "general" << endl; }
    };
    
    
    template<typename T>
    class A<int, T> {
    public:
        A() { cout << "first int" << endl; }
    };
    
    template<typename T>
    class A<T, double> {
    public:
        A() { cout << "first double" << endl; }
    };
    
    A<char, char> a;
    A<int ,char> b;
    A<char, double> c;
    
    // 特列化参数的一部分
        template<typename T>
    class A { 
    public:
        A() { cout << "general " << endl;}
    };  
        
    template<typename T>
    class A<T&> {
    public:
        A() { cout << "reference " << endl;}
    };  
        
    template<typename T>
    class A<T*> {
    public:
        A() { cout << "pointer " << endl;}
    };  
        
    A<int> a;
    A<int&> b;
    A<int*> c;
    
    
    
    
posted @ 2021-05-04 16:42  殷大侠  阅读(791)  评论(1编辑  收藏  举报