c++泛型编程与模板-02类模板

类模板详解

类模板的定义及实例化

template<class 模板参数>
class 类名 {
    // 类定义
};

template<typename 模板参数>
class 类名 {
    // 类定义
};

其中: template是声明类模板的关键字,表示声明一个模板,模板参数可以是一个,也可以是多个,可以是类型参数 ,也可以是非类型参数。类型参数由关键字class或typename及其后面的标识符构成。非类型参数由一个普通参数构成,代表模板定义中的一个常量。

一般情况下,类模板的声明和实现在一个头文件内完成。如果采用声明实现分离的方式,在.h文件中声明,在.hpp文件中实现,使用该类模板时include目标.hpp文件。
Example01

// mytemplate.h
template<class T>
class MathOp {
public:
    T add(T &a, T &b);
};
// mytemplate.hpp
#include "mytemplate.h"

template<class T>
T MathOp<T>::add(T &a, T &b)
{
    T res = a + b;
    return res;
}

// main.cpp
#include 

#include "mytemplate.hpp"

int main(int argc, char** argv)
{
    MathOp<int> op;
    int         a   = 1;
    int         b   = 2;
    auto        res = op.add(a, b);

    std::cout << "1 + 2 = " << res << std::endl;
    return 0;
}
// type为类型参数,width为非类型参数
template<class type, int width>
  • 如果在全局域中声明了与模板参数同名的变量,则该变量被隐藏掉
  • 模板参数名不能被当作类模板定义中类成员的名字
  • 同一个模板参数名在模板参数表中只能出现一次
  • 在不同的类模板或声明中,模板参数名可以被重复使用
    typedef string type;
    template <class type, int width>
    class Graphics {
        type           node;  //* node 不是string类型
        typedef double type;  ///! error: 成员函数不能与模板参数type同名
    };
    
    template <class T, class T>  ///! error:重复使用名为T的参数
    class Rect;
    
    template <class T>  //* 参数名"T"在不同模板间可以重复使用
    class Round;
    
  • 在类模板的前向声明和定义中,模板参数的名字可以不同
    /// MyStack声明都引用同一个类模板的声明
    template <class T>
    class MyStack;
    
    template <class U>
    class MyStack;
    
    template <class Tp>
    class MyStack {
        //* 模板定义中智能引用名字"Tp",不能引用"T"和"U"
    };
    
  • 类模板参数可以有缺省实参,给参数提供缺省实参的顺序是先右后左
    template <class T, int size = 16>
    class MyStack;
    
  • 类模板名可以被用作一个类型指示符。当一个类模板名被用作另一个模板定义中的类型指示符时,必须指定完整的实参表
    template <typename T>
    class Graphics {
        T* next;  //* 在类模板自己的定义中不需要指定完整模板参数表
    };
    
    template <typename T>
    void show(Graphics<T>& g)
    {
        Graphics<T>* pg = &g;  //* 必须指定完整的模板参数表
    }
    

类模板实例化

从通用的类模板定义中生成类的过程称为模板实例化

//* T是一个形参,同类型的实参值被提供给该形参
//* 指定的每个不同类型值都创建一个新类
template <typename T>
class Graphics {
    T m_value;
};
// 类实例化
//* T被指定为int
class Graphics {
    int m_value;
};
//* T被指定为double
class Graphics {
    double m_value;
};
//* T被指定为string
class Graphics {
    string m_value;
};

类模板实例化分为显示实例化隐士实例化

  • 显示实例化
    template class Stack<int>; //将类模板实例化为一个处理int类型的Stack类
    
  • 隐式实例化
    Stack<char> charStack; // 先实例化一个CharStack类(名字由编译器按规则生成),然后用CharStack char Stack;创建一个对象
    Stack<int> intStack; // 实例化一个IntStack类,在调用IntStack intStack;创建一个对象
    

类模板实例化的时机

  • 当使用了类模板实例的名字,并且上下文环境要求存在类的定义时
  • 对象类型是一个类模板实例,当对象被定义时。此点被称作类的实例化点
  • 一个指针或引用指向一个类模板实例,当检查这个指针或引用所指的对象时
    #include 
    
    template <typename T>
    class Graphics {
        // body
    };
    
    void func(Graphics<char>);  // 函数声明,不需要实例化
    
    class Rect {
        Graphics<double>& rsd;  // 声明一个类模板引用,不需要实例化
        Graphics<int>     si;   // si是Graphics类型的对象,需要实例化类模板
    };
    
    void func(Graphics<char> p)
    {
        // todo
    }
    
    int main(int argc, char** argv)
    {
        Graphics<char>* sc;  // 声明一个类模板指针,不需要实例化
    
        func(*sc);  // 需要实例化,传递给函数func的是一个Graphics对象
    
        auto iobj = sizeof(Graphics<std::string>);  // 需要实例化,因为sizeof会计算Graphics对象的大小,
                                                    // 为了计算大小,编译器必须根据类模板定义产生该类型。
        return 0;
    }
    

类模板的成员函数

  • 类模板的成员函数可以在类模板的定义中定义(inline函数),也可以在类模板定义之外定义(此时成员函数定义前面必须加上template及模板参数)
  • 类模板成员函数本身也是一个模板,类模板被实例化时它并不自动被实例化,只有当它被调用或取地址,才被实例化
  • 类中的虚函数不能用类型参数模板
    template <typename T>
    class Graphics {
    public:
        Graphics() {}
        // 成员函数定义在类模板的定义中
        void print() {}
    
        // 类中的虚函数不能用类型参数模板
        // template 
        // virtual void func(U value) {
        // 
        // }
    private:
        T m_value;
    };
    
    // 成员函数定义在类模板定义之外
    template <typename T>
    void Graphics<T>::print()
    {
    }
    
  • 普通类的成员函数模板
    #include 
    // 普通类
    class MyTest {
    public:
        // 成员函数模板
        template <typename U>
        void func(U val)
        {
            std::cout << "type=" << typeid(val).name() << " value:" << val << std::endl;
        }
    };
    
    int main()
    {
        MyTest test;
        test.func(100); // 普通类的成员函数模板:自动类型推导为int
        test.func(10.0);// 普通类的成员函数模板:自动类型推导为double
        return 0;
    }
    // 输出
    // type=i value:100
    // type=d value:10
    
  • 类模板的成员函数模板
    #include 
    
    // 类模板
    template <typename T>
    class MyTest {
    public:
        MyTest(T val) : m_val(val) {}
        // 普通成员函数
        void print()
        {
            std::cout << "print() type=" << typeid(m_val).name() << " m_val:" << m_val << std::endl;
        }
        // 成员函数模板
        template <typename U>
        void func(U val)
        {
            std::cout << "template type=" << typeid(val).name() << " value:" << val << std::endl;
        }
    
    private:
        T m_val;
    };
    
    int main()
    {
        MyTest<int> test(100);  // 类模板实例化,显示指定类型T-> int
        test.print();
        test.func(10.0);  // 类模板的成员函数模板,自动推导出U-> double
    
        MyTest<double> test1(100.0);  // 类模板实例化,显示指定类型T-> double
        test1.print();
        test1.func(10);  // 类模板的成员函数模板,自动推导出U-> int
    
        return 0;
    }
    // 输出
    // print() type=i m_val:100
    // template type=d value:10
    // print() type=d m_val:100
    // template type=i value:10
    

拷贝构造函数模板与拷贝赋值运算符模板

template <typename T>
class MyTest {
  public:
    // 构造函数模板
    template <typename U>
    MyTest(U value)
    {
        std::cout << "类模板泛化" << std::endl;
    }
    // 拷贝构造函数模板
    template <typename T1>
    MyTest(const MyTest<T1>& other)
    {
        std::cout << "拷贝构造函数模板" << std::endl;
    }
    // 拷贝赋值运算符模板
    template <typename T2>
    MyTest<T> & operator=(const MyTest<T2>& other)
    {
        std::cout << "拷贝赋值运算符模板" << std::endl;
    }
    // 成员函数模板
    template <typename V>
    void func(V param)
    {
        std::cout << "func param" << param << std::endl;
    }

    T          m_value1;
    static int m_static_value;  // 静态成员变量
};

类模板特例化

类模板的特化分为全特化与偏特化两种方式
全特化:对于全特化,类的所有参数都与模板类的所有参数一一对应

  • 普通成员函数全特化
  • 静态成员变量全特化
    template <typename T, typename U>
    struct MyTest
    {
        MyTest()
        {
            std::cout << "类模板泛化" << std::endl;
        }
    
        void func()
        {
            std::cout << "func函数泛化" << std::endl;
        }
        static  int  m_value;// 静态成员变量
    };
    
    // 普通成员函数全特化
    template <>
    void MyTest<int, double>::func()
    {
        std::cout << "func全特化" << std::endl;
    }
    
    template<typename T, typename U>
    int MyTest<T, U>::m_value = 10;
    
    // 静态成员函数全特化
    template<typename T, typename U>
    int MyTest<int, double>::m_value = 10;
    

偏特化:偏特化位于全特化和模板类之间,只对模板类的部分参数进行显示声明。类模板的偏特化主要有以下两个方面:一个是类模板参数变量的偏特化;另外一个是类模板参数范围上的偏特化;

  • 模板参数数量上的偏特化:在某个固定变量类型,其它变量未确定的偏特化类模板
  • 模板参数范围上的偏特化:变量作用域变小,T-> const T, T-> T*等
  • 类的成员函数、成员变量不支持独立偏特化
    // 模板类
    template <typename T1, typename T2>
    class Base {
    public:
        void func(T1& a, T2& b)
        {
            std::cout << "模板类: a = " << a << " b = " << b << std::endl;
        }
    };
    
    // 全特化
    // T1->int, T2-> double
    template <>
    class Base<int, double> {
    public:
        void func(int& a, double& b)
        {
            std::cout << "全特化类: a = " << a << " b = " << b << std::endl;
        }
    };
    
    // 偏特化
    // T1->默认为模板参数,T2-> double
    template <typename T>
    class Base<T, double> {
    public:
        void func(T& a, double& b)
        {
            std::cout << "偏特化类: a = " << a << " b = " << b << std::endl;
        }
    };
    

全特化类与偏特化类优先级:全特化 > 偏特化 > 模板类

类模板的嵌套特化

#include 

template <typename T1>
class Outter {
  public:
    Outter()
    {
        std::cout << "General Outter.\n";
    }

    template <typename T2>
    class Inner {
      public:
        Inner()
        {
            std::cout << "General Inner.\n";
        }
    };
};

// 嵌套特化
template <>            // 模板类的模板参数:Outter 特例化
template <typename T>  // 模板类的模板成员类的模板参数:Inner 保持原样
class Outter<int>::Inner {
  public:
    Inner()
    {
        std::cout << "Specialized Inner.\n";
    }
};

int main(int argc, char **argv)
{
    Outter<int>::Inner<int>    a;
    Outter<double>::Inner<int> b;
    return 0;
}

类模板的友元

  • 普通类作为类模板的友元
    #include 
    // 类模板
    template <typename T>
    class A {
    private:
        T m_value;
        friend class B;
    };
    // 普通类
    class B {
    public:
        void func()
        {
            A<int> a;
            a.m_value = 10;
            std::cout << a.m_value << std::endl;
        }
    };
    
    int main(int argc, char** argv)
    {
        B b;
        b.func();
        return 0;
    }
    
  • 特化版类模板作为另外一个类模板的友元
    #include 
    
    template <typename U>
    class B;
    
    // 类模板
    template <typename T>
    class A {
    private:
        T m_value;
        friend class B<double>;  // 类模板B的特化
    };
    
    template <typename U>
    class B {
    public:
        void func()
        {
            A<double> a;  //
            a.m_value = 10;
            std::cout << a.m_value << std::endl;
        }
    };
    
    int main(int argc, char** argv)
    {
        B<double> b;  // 必须和A中friend指定的参数一致
        b.func();
        //---------------------------
        // compile error
        // B b1;
        // b1.func();
        /*
        main.cpp: In instantiation of ‘void B::func() [with U = int]’:
        main.cpp:31:12:   required from here
        main.cpp:20:11: error: ‘double A::m_value’ is private within this context
        20 |         a.m_value = 10;
            |         ~~^~~~~~~
        main.cpp:10:7: note: declared private here
        10 |     T m_value;
            |       ^~~~~~~
        main.cpp:21:24: error: ‘double A::m_value’ is private within this context
        21 |         std::cout << a.m_value << std::endl;
            |                      ~~^~~~~~~
        main.cpp:10:7: note: declared private here
        10 |     T m_value;
        */
        return 0;
    }
    
  • 泛化版类模板作为另一个类模板的友元
    #include 
    
    template <typename U>
    class B;
    
    // 类模板
    template <typename T>
    class A {
    private:
        T m_value;
        template <typename U>
        friend class B;
    };
    
    template <typename U>
    class B {
    public:
        void func()
        {
            A<double> a;
            a.m_value = 10;
            std::cout << a.m_value << std::endl;
        }
    };
    
    int main(int argc, char** argv)
    {
        B<double> b;
        b.func();
    
        B<int> b1;
        b1.func();
        return 0;
    }
    
  • 类类型参数做类模板的友元

类模板的继承

  • 父类是特化的模板类,子类是普通类
      class Derived : public Base {}
    
  • 父类是普通类,子类是模板
    template 
    class Derived : public Base {}
    
  • 父类和子类都是模板
    template 
    class Derived : public Base {}
    
    • 如果父类和子类都是模板,子类在访问父类的数据成员和函数成员时,必须使用this->或者Base::对目标成员进行限定

你可能感兴趣的:(C/C++,c++)