美文网首页程序猿
C++11泛型 - 类模板

C++11泛型 - 类模板

作者: 许了 | 来源:发表于2019-03-03 21:28 被阅读24次

    前面我们介绍了函数模板。今天我们来看看C++的另一种泛型:类模板。C++中类模板通常是容器(如std::vector)或行为的封装(如之前我们实现的chan<T>类)。类模板语法:

    template < parameter-list > class-declaration
    

    构成类模板的形参(parameter-list)约束与函数模板相同,此处就不赘述了。与函数模板的类型自动推演不同,类模板实例化时,需要显式指定:

    std::vector<int> intArr;
    

    一、成员函数

    与函数模板一样,类模板只是定义了一组通用的操作,在具体实例化前是不占用程序空间的。这种Lazy特性在类模板中得到了进一步地加强:成员函数(含成员函数模板)只有在使用时才生成

    template<typename T>
    class A {
        T a_;
    public:
        void add(int n) {
            a_ += n;
        }
    };
    
    class M{};
    
    int main() {
        A<M> s;      // s未用到add函数,因此整个程序得以成功编译
       // s.add(1);  // 如果有这句话,则会编译失败
    }
    

    本例中,A<T>::add在变量s中未使用到,因此虽然a_ += n不合法,但整个程序仍然通过了编译。

    1.1 虚函数

    在函数模板中我们提到虚函数不能是函数模板,那么在类模板中可以有虚函数吗?答案是肯定的,类模板中可以有虚函数,虚函数在类模板实例化为模板类时由编译器生成,因此其实现必须是合法的,否则即使未被使用到,也会编译失败。类模板的虚函数可以访问模板类中的泛型成员(变量、成员函数模板都可以访问)。

    #include <iostream>
    template<typename T>
    class A {
        T a_;
    public:
        virtual void say() {
            std::cout << "a -> " << a_ << std::endl;
        }
    };
    
    class M{};
    
    int main() {
        // 尽管say函数未被使用,此处会编译仍会失败,因为std::cout << m.a_操作是非法的
        A<M> m;
    }
    

    1.2 成员函数模板

    类模板和函数模板结合就是成员函数模板。

    #include <iostream>
    template<typename T>
    class Printer {
        T prefix_;
    public:
        explicit Printer(const T &prefix):prefix_(prefix){
        }
        // 成员函数模板
        template<typename U, typename ...Args> void print(const U &u, Args... args);
        void print() {
            std::cout << std::endl;
        }
    };
    
    template<typename T> template<typename U, typename ...Args>
    void Printer<T>::print(const U &u, Args... args) {
        std::cout << this->prefix_ << u << std::endl;
        print(args...);
    }
    

    二、类模板特化与偏特化

    模板特化是指定类模板的特定实现。是针对某类型参数的特殊化处理。假设我们有一个类模板Stack<T>,它有一个功能:min(取Stack的最小值),则该类模板的典型实现如下:

    template<typename T>
    struct StackItem {
        StackItem *next;
        T item;
    };
    template<typename T>
    class Stack {
        StackItem<T> *front = nullptr;
    public:
        T min() const {
            assert(front != nullptr);
            T min = front->item;
            for (StackItem<T> *it = front->next; it != nullptr; it = it->next) {
                if (it->item < min) {
                    min = it->item;
                }
            }
            return min;
        }
    };
    

    Stack<T>::min所需满足的契约是:T需支持小于操作(operator <)。但有些类型无法满足该要求,如const char *。如果Stack<T>要支持const char *的话,则需要特化。

    template<>
    class Stack<const char *>  // 类名后面,跟上<...>,则表明是特化
    {
        StackItem<const char *> *front = nullptr;
    public:
        const char * min() const {
            assert(front != nullptr);
            const char * min = front->item;
            for (StackItem<const char *> *it = front->next; it != nullptr; it = it->next) {
                if (strcmp(it->item, min) < 0) {
                    min = it->item;
                }
            }
            return min;
        }
    };
    

    2.1 偏特化

    偏特化也叫部分特化,指的是当类模板有一个以上模板参数时,我们希望能对某个或某几个模板实参进行特化。类模板的特化(或偏特化)只需要模板名称相同并且特化列表<>中的参数个数与原始模板对应上即可,模板参数列表不必与原始模板相同模板名称相同。一个类模板可以有多个特化,与函数模板相同,编译器会自动实例化那个最特殊的版本。

    类模板特化和偏特化
    完全特化的结果是一个实际的class,而偏特化的结果是另外一个同名的模板。

    三、类模板中的static成员

    类模板中可以声明static成员。但需要注意的是每个不同模板实例都会有一个独立的static成员变量

    template<typename T>
    class A {
    public:
        static int count;
    };
    template<typename T> int A<T>::count = 1;
    

    A<int>::countA<double>::count是不同的两个变量。

    3.1 类模板中static成员的特化

    static成员也可以进行特化

    // template<typename T> A {...};  // 见上面的定义
    template<> int A<const char *>::count = 100;
    

    则A<const char *>::count的值被值始化为100,而以其它类型进行实例化时则初始化为1。

    四、友元

    友元在C++中做为一个BUG式的存在,可以授权“好友”访问其隐私数据。

    template<typename T> class A;  //  前置声明,在B中声明友元需要的
    
    template<typename U>
    class B {
        // 每个B的实例将授权相同类型实例化的A
        friend class A<U>;
        // C的所有实例都是B的友元,该种情况下C无需前置声明
        template<typename T> fiend class C;
        // 普通类
        friend class D;
        // 模板自己的类型参数成为友元
        friend U;
    };
    

    五、总结

    本节简单介绍了类模板,由于篇幅限制不能一一展开,如有疏漏欢迎批评指正。

    上一篇
    C++11多线程-函数模板
    目录 下一篇

    相关文章

      网友评论

        本文标题:C++11泛型 - 类模板

        本文链接:https://www.haomeiwen.com/subject/biujrqtx.html