• C++ Primer 5th 第16章 模板与泛型编程


    模板是C++中泛型编程的基础,一个模板就是创建一个类或者函数的蓝图或者说公式。

    C++模板分为函数模板和类模板。C++根据调用模板时传入的具体类型来生成相应类型的具体函数或者类。

    类模板则可以是整个类是个模板,类的某个成员函数是个模板,以及类本身和成员函数分别是不同的模板。

    1.函数模板

    函数模板以关键字template开始,后接尖括号括起来的模板参数列表,模板参数列表不允许是空的,也即模板参数至少有一个或多个,多个之间使用逗号分割。

    模板参数表示的是函数中用到的类型或者是一个值。当我们使用模板时,根据提供的实参推断出实参的类型,该类型即被用于绑定到模板参数,这个过程被叫做模板的实例化,相应的,生成的版本叫做模板的实例。

    模板参数表示的是类型,该类型可以用于制定函数的返回类型或者函数的参数类型,也可以用于函数体内变量声明定义等。

    对于每个模板参数列表中的每个类型参数,其前面必须加上关键字typename或class。关键字typename和class之间没有区别。

    除了定义模板的类型参数,还可以定义一种非类型的参数。非类型参数不表示一种类型,而是表示一个值。非类型参数也不使用关键字typename和class,而是使用具体的类型来指定。

    非类型参数在模板实例化时,被用户或者编译器推断出的值所代替,该值必须是常量表达式。(常量表达式是指值不会改变,并且编译期间就能计算得出结果的表达式,字面值属于常量表达式,常量表达式初始化的const对象也是常量表达式)。

    例如:

    template <unsigned N, unsigned M>
    int fun(const char (&p1)[N], const char (&p2)[M])
    {
        return strcmp(p1, p2);
    }

    这里的N,M将会被我们调用fun时传入的实参的值替代:

    fun("hi", "mom");

    编译器会使用字面常量的大小来替代N,M,并在字符串字面值末尾插入一个空字符作为终止标记,最终实例化为:

    int fun(const char (&p1)[3], const char (&p2)[4]);

    非类型参数可以是整形,也可以是指针或者引用。条件是整形是常量表达式,而指针和引用必须是关联static对象。

    模板可以声明为inline和constexpr的。

    当编译器对代码进行编译时,在源码模板定义部分,编译器并没有实际去生成相应的模板代码,只有在使用模板实例化一个具体的版本时,编译器才生成相应的代码。

    2.类模板

     与函数模板不同,类模板不能为其模板类型参数进行推断。为什么不能为类模板推断类型参数?因为定义类对象的时候,有可能无法提供足够的类型来让编译器进行推断,比如 vector v;这里仅仅定义了一个vector对象,没有提供任何额外的信息让编译器来推断模板参数,所以类模板不为其模板类型参数进行推断,必须我们在实例化时显式指明。

    类模板的名字不是一个类,也即不是自定义的一个class,而是一个生成class的说明模板。因此使用模板生成的类,必定是带模板类型实参的class,所以一个实例化的class必然有<>来指明类型参数。

    类模板的成员函数可以在内部定义,这样是隐式内联。也可以在外部定义。类模板的成员函数是一个普通的成员,而不是模板。虽然在外部定义时,成员函数需要以关键字template开始,并且后接模板参数列表,但这不表示该成员函数是个模板。之所以需要关键字template和模板参数列表,是因为成员函数所属的类在实例化时,会具体绑定到一个特定的类型上,成员函数也需要相应被动地绑定到该类型。

    类模板的成员函数只有被用到的时候才会进行实例化,如果没有被用到,就不会实例化,如同类中定义的成员函数一样,如果不会被用到,那么可以只声明而不定义它。

    当我们在类模板作用域内进行模板成员的定义时,可以省略模板实参。

    template <typename T>
    class BlobPtr
    {
    public:
        BlobPtr& operator++();        //无需说明具体类名BlobPtr<T>
        BlobPtr& operator--();        //无需说明具体类名BlobPtr<T>
        BlobPtr  operator--(int);     //无需说明具体类名BlobPtr<T>
    
        bool empty()
        {
            return data->empty();
        }
    
        size_t size();
    
    private:
        std::shared_ptr<std::vector<T>> data;
    };
    
    template <typename T>
    size_t BlobPtr<T>::size()    //类作用域外,需说明具体类名BlobPtr<T>
    {
        return data->size();
    }

     当我们在类模板的外面定义成员函数时,必须以关键字template开始,后接类模板参数列表。

    这里为什么类成员函数size( )前要加模板参数列表<T>?template<typyname T>不已经指明了类型了吗? 

    前面已经说过:类模板的成员函数是一个普通的成员,而不是模板。这里的template <typename T>是说明size( ) 所属的BlobPtr是个模板类,并且BlobPtr

    本身也不是一个类名,是类模板名,真正的类名是BlobPtr<T>,说白了,其实是在具体指明类作用域。所以在类外定义成员函数时,成员函数的参数列表对模板参数是可以省略的。

    因此在类模板外面定义普通非模板成员函数时,首先用template <typename T>说明是一个类模板,然后再具体说明该成员函数是哪个类中的成员。后面会看到,如果模板类的成员函数也是一个模板,那么就需要分别各自说明类的模板和函数的模板。

    3.类模板和友元

    如果一个类模板包含一个非模板友元,则友元可以访问该类模板的所有实例。

    如果类和友元都是模板,则类实例可以对友元所有实例授权,也可以只授权给特定实例。

    为了让所有实例成为友元,友元声明中必须使用与类模板本身不同的模板参数。

    在C++11中,我们也可以模板类型参数声明成友元。例如:

    template <typename Type>
    class Bar
    {
        friend Type;
        //  ...
    };

    新标准也允许我们为类模板定义一个类型别名:

    template <typename T> using twin = pair<T, T>;
    twin<string, string> authors;    //authors是一个pair<string, string>

    模板参数遵循普通的作用域规则。与任何其他名字一样,模板参数会隐藏外层作用域中声明的相同名字,需要注意的是,在模板内不能重用模板参数名。例如:

    typedef double A;
    template <typename A, typename B> void f(A a, B b)
    {
        A tmp = a;  //类型A隐藏外部typedef double A
        double B;   //错误,不允许重用模板类型参数
    }

    模板声明必须包含模板参数。与函数参数相同,声明中的模板参数的名字不必与定义中相同。

    对于一个给定的模板的声明和定义必须有相同数量和种类的参数。

    关于typename可以用做模板参数的关键字,也可以用来指示类型还是变量名。具体可以参考这篇文章:http://feihu.me/blog/2014/the-origin-and-usage-of-typename/

    4.类模板的static成员

    与普通类一样,模板类也可以拥有static成员,如下:

    template <typename T>
    class Foo
    {
    public:
        static std::size_t count() { return ctr; }  //声明并定义
    private:
        static std::size_t ctr;    //声明,尚未定义
    };

    上面这段代码,Foo是一个类模板,它实例化后的类有一个count的静态成员函数和一个静态ctr数据成员。

    类的static数据成员有且只有一个定义,类模板也是如此。因此,我们将需要在类模板外定义ctr数据成员,类模板外定义静态数据成员的格式是template关键字开始,后跟模板参数列表,如下:

    template <typename T>
    size_t Foo<T>::ctr = 0;

    上面代码中类名是带模板参数的,因为实例化后的静态数据成员是具体属于某一个类的,而一个具体类则是带模板实参的,仅有类名则只是一个类模板的名字。

    在新标准中,我们还可以为函数模板和类模板提供默认模板实参,对于类模板,当使用默认实参时,只需使用空的尖括号<>来表示即可。

    5.成员模板

    一个普通类或者类模板可以包含一个模板的成员函数。这种成员被称为成员模板。成员模板不允许是虚函数。

    对于类模板,其类和成员有各自独立的模板参数。

    与类模板的普通成员函数不同,成员模板是函数模板。当我们在类模板外面定义实现成员模板时,要同时提供类模板和成员模板的参数列表。其中,类模板的参数列表在前,成员模板的在后。例如:

    template <typename T>
    class Blob
    {
        template <typename It> Blob(It b, It e);
        // ...
    };
    
    template <typename T>     //类模板的参数列表
    template <typename It>     //成员模板参数列表
    Blob<T>::Blob(It b, It e):data(std::make_shared<std::vector<T>>(b, e))
    {
        //constructor
    }

    对于含成员模板的类模板,在实例化时,需要同时提供类模板实参和成员模板实参,对于类模板实参,则是显式提供,对于成员模板的实参,则是自动推断。

    6.控制实例化

    模板只有被用到时,编译器才会根据实参进行实例化实参相应的代码,在不同源文件中提供相同实参实例化同一模板时,将会在不同文件中重复生成相同的实例,每个源文件中都会有一个实例。大系统中,这会导致严重的额外开销。

    C++11新标准中,可以使用显式实例化来避免这种额外开销。方法是使用实例化声明和实例化定义。

    形如:

    extern template declaration;     //实例化声明
    template declaration;             //实例化定义

    下面是实际的例子
    extern template class Blob<string>;              // 声明
    template int compare(const int&, const int&);     // 定义

    上面的代码中,第一行是声明,第二行是定义。需要注意的是,模板的实例化控制的声明和定义是配套使用的。

    当编译器遇到extern模板声明时,编译器就不再实例化,它会去程序的其他地方寻找实例,可以多次声明,但只能一次定义。

    由于编译器会在使用模板时自动实例化,因此extern声明必须在任何使用当前模板前面声明。

    当编译器遇到一个模板的实例化控制的定义时,编译器将会进行实例化,以生成代码。

    类模板的实例化定义会实例化模板的所有成员,而不是用到哪个成员才实例化哪个成员。

    7.

  • 相关阅读:
    dedecms自定义表单提交获取时间跟ip地址
    JQuery购物车多物品数量的加减+总价计算
    jquery手机触屏滑动拼音字母城市选择器代码
    js实现图片上传实时显示
    js实现发送验证码倒计时效果
    JS 仿支付宝input文本输入框放大组件
    js实现倒计时效果
    jquery统计输入文字的个数并对其进行判断
    【Linux】Linux系统安全设置
    java泛型(generics)
  • 原文地址:https://www.cnblogs.com/pluse/p/5968685.html
Copyright © 2020-2023  润新知