C++模板简介

目录

1 什么是C++中的模板

1.1 模板的定义

1.2 使用模板的好处

2 怎么用模板?

2.1特化——解决代码从模板到具体化的问题

2.1.1 模板参数指定

2.1.2 隐式特化

2.1.3 显示特化

2.2 模板实例化

2.2.1 显式实例化

2.2.2隐式实例化

3 示例

3.1显示特化实例

3.2全特化实例

3.3偏特化实例

3.4函数模板例子

3.5 类模板实例

4 总结

5 主要参考资料


1 什么是C++中的模板

1.1 模板的定义

CppReference中定义:

A template is a C++ entity that defines one of the following:

  • (1)a family of classes (class template), which may be nested classes
  • (2)a family of functions (function template), which may be member functions
  • (3)an alias to a family of types (alias template) (since C++11)

从定义中可以看出,他可以定义一族类、一族函数或者一族类型的别名。通过模板可以生成多种具体的类和函数,这些生成的类和函数之间没有什么联系。模板就像要制造某种物件的模具,模具本身和最终的产品没有必然关系。

例如下面一个模板定义:
 

template<typename T> 

class TestTemp{}; 

模板名为TestTemp,使用改模板生成类时,必须制定一个具体的类型T,如要生成TestTemp<int>和TestTemp<char>两个类,应该写成:

TestTemp<int> a;  // TestTemp<int>是类,a是对象;

TestTemp<char> b;   

生成的TestTemp<int>和TestTemp<char>毫无关系,既不是相同类型也没有继承关系。

引用CppReference上的一句话:

“A class template by itselfis not a type, or an object, or any other entity”。

意为:一个类模板自身并不是一个类型,或对象,或其他任何实体。换言之,必须通过其生成具体的类后才能使用,那么这个过程其实就是我们所谓的特化

1.2 使用模板的好处

·  所谓模板是一种使用无类型参数来产生一系列函数的机制。

·  若一个程序的功能是对某种特定的数据类型进行处理,则可以将所处理的数据类型说明为参数,以便在其他数据类型的情况下使用,这就是模板的由来。

·  模板是以一种完全通用的方法来设计函数或类而不必预先说明将被使用的每个对象的类型。

·  通过模板可以产生类或函数的集合,使它们操作不同的数据类型,从而避免需要为每一种数据类型产生一个单独的类或函数

使用模板的优点摘自作者:山的那边是什么_   链接:https://www.jianshu.com/p/8156b4aae3bd


2 怎么用模板?

2.1特化——解决代码从模板到具体化的问题

所谓特化,就是根据模板实参来生成相应的、具体的类或者函数。换句话说,当使用具体的模板实参来替换模板参数列表里面的每一个形参后生成的类或函数的代码,就是对应这个模板实参的特化。比如上面的TestTemp<int>,那么此时就会使用int来替换类模板中的T,当替换后会生成一个针对int这个模板实参的类定义,那么这个类定义TestTemp<int>便是TestTempint上的特化

特化根据方式不同,又可以分为模板参数指定、显示特化和隐式特化。

2.1.1 模板参数指定

对于类模板而言,在使用的时候所有的模板参数都是必须要指定的,比如说你必须通过TestTemp<int>这样的方式明确指定出模板实参为int。

2.1.2 隐式特化

隐式特化主要针对函数模板,调用函数模板时,它会根据传入的函数参数(Function Argument)推导出模板实参(Template Argument),当然了传入的参数必须是可推导的,当函数模板推导出模板实参后,可以再根据推导结果做模板特化,这种推导特化的方式就叫隐式特化(Implicit Specialization。当然,函数模板也支持显示的模板参数指定方式。

 

2.1.3 显示特化

除了隐式特化外对应的还有显式特化(Explicit Specialization,考虑这样一种情况,比如说上面的TestTemp类模板,当其模板参数为int和char的时候该模板都能很好的工作,但是当其模板参数为bool的时候我们需要一种新的定义来满足bool类型对该模板的特殊需求。此时,如果按照原有的TestTemp的定义生成TestTemp<bool>将无法满足需求(有特殊功能需求),那么这样我们就需要对bool做专门的处理,既重新对bool专门定制一份新的定义而其他的类型仍旧按照原有的类模板TestTemp的定义来生成特化,那么这样就叫做模板的显式特化(Explicit Specialization,或者叫全特化(Full Specialization,与其对应的还有偏特化(Partial Specialization)。

2.2 模板实例化

特化是解决由模板生成具体代码的问题,而实例化解决将具体代码进行编译的问题

所谓的实例化,就是说把对应的模板特化的代码真正的编译进去。我们之前说过一个类模板本身既不是一个类型也不是一个对象,可以说它什么实体都不是,必须要在其实例化之后才能真正的使用它,那么实例化的概念就是提供模板的全部参数,编译器便会根据所提供的参数来生成具体的代码,如此以后才可以使用。

 

2.3 特化和实例化的区别

实例化概念和之前的特化有点相似但也有些不同,特化的意思是根据模板的参数来确定具体的代码,无论是显示特化还是隐式特化,总之它会生成或者找到需要使用的代码,而实例化的过程是说要把代码编译进去

实例化也包括了隐式和显式两种,先看一下显式实例化的方法。

2.2.1 显式实例化

 

template class-name<template arguments>;

看起来格式上十分简单,只需要通过上面一句简单的定义实例化就可以完成了,

我们先看一个简单的例子:

    #include <iostream>
    using namespace std;
    template<typename T>
    class Test
    {

    public:
        void testInterface()
        {
            cout << "testInterface" << endl;
        }
    };
    int main()
    {
        return 0;
    }

 

上面这个例子中定义了一个简单的类模板,包含了一个名为testInterface的成员函数,使用g++ -S 命令对其进行汇编,得到一个Test.S的汇编文件,如下


             .file  "Test.cpp"

             .local         _ZStL8__ioinit

             .comm      _ZStL8__ioinit,1,1

             .text

             .globl        main

             .type         main, @function

    main:

    .LFB972:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             movl          $0, %eax

             popq         %rbp

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE972:

             .size main, .-main

             .type         _Z41__static_initialization_and_destruction_0ii, @function

    _Z41__static_initialization_and_destruction_0ii:

    .LFB973:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             subq          $16, %rsp

             movl          %edi, -4(%rbp)

             movl          %esi, -8(%rbp)

             cmpl          $1, -4(%rbp)

             jne    .L3

             cmpl          $65535, -8(%rbp)

             jne    .L3

             movl          $_ZStL8__ioinit, %edi

             call   _ZNSt8ios_base4InitC1Ev

             movl          $__dso_handle, %edx

             movl          $_ZStL8__ioinit, %esi

             movl          $_ZNSt8ios_base4InitD1Ev, %edi

             call   __cxa_atexit

    .L3:

             leave

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE973:

             .size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii

             .type         _GLOBAL__sub_I_main, @function

    _GLOBAL__sub_I_main:

    .LFB974:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             movl          $65535, %esi

             movl          $1, %edi

             call   _Z41__static_initialization_and_destruction_0ii

             popq         %rbp

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE974:

             .size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main

             .section    .init_array,"aw"

             .align 8

             .quad        _GLOBAL__sub_I_main

             .hidden     __dso_handle

             .ident        "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"

             .section    .note.GNU-stack,"",@progbits

 

可以看出该汇编的代码中并没有任何和此模板相关的部分,咋看出来的呢,非常简单,搜关键字就可以,虽然C++中的标识符是会被重新编译的,但是编译后的标识符中一样会包含你所定义的名字,所以说白了就是模板部分的代码压根就没编进来,那么此时如果我们显式的实例化一下它,我们在main函数之上加入如下代码:

template class Test<int>;

好,此时我们再次来看看汇编结果:
           

  .file  "Test.cpp"

             .local         _ZStL8__ioinit

             .comm      _ZStL8__ioinit,1,1

             .text

             .globl        main

             .type         main, @function

    main:

    .LFB972:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             movl          $0, %eax

             popq         %rbp

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE972:

             .size main, .-main

             .section    .rodata

    .LC0:

             .string      "testInterface"

             .section    .text._ZN4TestIiE13testInterfaceEv,"axG",@progbits,_ZN4TestIiE13testInterfaceEv,comdat

             .align 2

             .weak       _ZN4TestIiE13testInterfaceEv

             .type         _ZN4TestIiE13testInterfaceEv, @function

    _ZN4TestIiE13testInterfaceEv:

    .LFB973:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             subq          $16, %rsp

             movq        %rdi, -8(%rbp)

             movl          $.LC0, %esi

             movl          $_ZSt4cout, %edi

             call   _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc

             movl          $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %esi

             movq        %rax, %rdi

             call   _ZNSolsEPFRSoS_E

             leave

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE973:

             .size _ZN4TestIiE13testInterfaceEv, .-_ZN4TestIiE13testInterfaceEv

             .text

             .type         _Z41__static_initialization_and_destruction_0ii, @function

    _Z41__static_initialization_and_destruction_0ii:

    .LFB982:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             subq          $16, %rsp

             movl          %edi, -4(%rbp)

             movl          %esi, -8(%rbp)

             cmpl          $1, -4(%rbp)

             jne    .L4

             cmpl          $65535, -8(%rbp)

             jne    .L4

             movl          $_ZStL8__ioinit, %edi

             call   _ZNSt8ios_base4InitC1Ev

             movl          $__dso_handle, %edx

             movl          $_ZStL8__ioinit, %esi

             movl          $_ZNSt8ios_base4InitD1Ev, %edi

             call   __cxa_atexit

    .L4:

             leave

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE982:

             .size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii

             .type         _GLOBAL__sub_I_main, @function

    _GLOBAL__sub_I_main:

    .LFB983:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             movl          $65535, %esi

             movl          $1, %edi

             call   _Z41__static_initialization_and_destruction_0ii

             popq         %rbp

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE983:

             .size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main

             .section    .init_array,"aw"

             .align 8

             .quad        _GLOBAL__sub_I_main

             .hidden     __dso_handle

             .ident        "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"

             .section    .note.GNU-stack,"",@progbits

很明显,Test<int>被编译进去了。于是,我们得出这样的结论,显式实例化就是告知编译器将类模板的一个特化版本的代码编译进去,如果大家还是不放心觉得汇编的代码也不一定会被编入最终的目标文件,那么则可以将此代码直接编译成Eexecute,然后用readelf来验证。

那么除了上述的这种直接实例化类模板的方式,还可以部分的实例化一个类,可以实例化的部分如下:

Classes

Functions

Member functions

Member classes

Static data members of class templates

此部分内容比较简单,我只想举一个例子,比如,上面的类模板,我可以单独来实例化他的testInterface函数:
 

template void Test<int>::testInterface();

//上面这句话的含义:模板关键字(template)    函数返回类型(void)  由模板生成的类(Test<int>)  类的函数(testInterface())

这样这个Test<int>的默认构造函数,析构函数等等都不会被实例化,那么有人要问,如果没有实例化构造函数,怎么创建对象,没有对象实例化成员函数又有什么用呢?这是个很好的问题,它的作用跟模板的编译方式有关系,我们先介绍隐式实例化,这个后面再聊。

2.2.2隐式实例化

好以上我们介绍了显式实例化,那么我们平时在使用一个类模板的时候很少用过这样的方法,为什么代码也被编译进去了呢?答案是还存在另一种实例化的方式--隐式实例化,那么对于隐式实例化的介绍我直接贴上cppreference上面的介绍:

When code refers to a template in context that requires a completely defined type, or when the completeness of the type affects the code, and this particular type has not been explicitly instantiated, implicit instantiation occurs. For example, when an object of this type is constructed, but not when a pointer to this type is constructed.(参见下面的示例)

 

限于英语是楼主的软肋,我的翻译只能当做一个参考,那么上面这段话的意思是,如果代码中引用了一个模板,并且需要一个完整的定义类型的时候,或者当一个类型的完整性影响到了代码,并且此时特定的类型没有被显式实例化,那么隐式实例化就会发生,它举了个例子,当要创建一个类型的具体对象的时候,特别强调的是类型的对象,而不是类型的指针对象。

 

那么就我个人而言对这个解释是不太理解的,主要是说的太泛泛,当然,也有可能是我英文实在太差没领悟到精髓。那么对此cppreference还举了几个具体的例子如下:
   

template<class T> struct Z {

        void f() {}

        void g(); // never defined

    }; // template definition

    template struct Z<double>; // explicit instantiation of Z<double>

    Z<int> a; // implicit instantiation of Z<int> 需要代码,前面又没有实例化时,就会隐式实例化;

    Z<char>* p; // nothing is instantiated here 定义指针时,不实例化;

    p->f(); // implicit instantiation of Z<char> and Z<char>::f() occurs here. 将指针指向模板中某个函数时,再进行隐式实例化;

    // Z<char>::g() is never needed and never instantiated: it does not have to be defined

注释的已经很详细了,就不再解释了,要说的是首先当你切实用到了一个类模板的时候才会被实例化,而且,这个例子中还表达了另一层意思,他说Z<char>和Z<char>::f()会被实例化,而Z<char>::g()不会,那么是不是说模板是按部分来实例化的呢?答案是YES,其实按照我的了解,不仅仅是模板,包括一个普通的类,类的成员在编译的时候也不是都会编译进去的,这个情况比较复杂,要看你怎么编译在此就不多说了,我们只来看模板,对于模板的编译,如果隐式实例化,那么就是用到多少编译多少,没用的的不编。当然显式实例化也是可以按部分来实例化的,这个上面有说到。

好,那么按照cppreference上面的定义来讲,特化,无论是显式还是隐式,它跟实例化是分开的两码事,那么我们来看下面这个例子,


    #include <iostream>

    

    using namespace std;

    

    template<typename T>

    class Test

    {

    public:

        void testInterface()

        {

            cout << "testInterface" << endl;

        }

    };

    

    template<>

    void Test<int>::testInterface()

    {

        cout << "Test<int>::testInterface" << endl;

    }

    

    int main()

    {

        return 0;

    }

这个例子里面对Test<int>做了特化,那么按照我之前个人的理解,因为Test<int>的testInterface函数没用被调用,所以不会被实例化进去,也就是说不会编译,为了验证我的想法我将其汇编后结果如下:

 .file  "Test.cpp"

             .local         _ZStL8__ioinit

             .comm      _ZStL8__ioinit,1,1

             .text

             .align 2

             .globl        _ZN4TestIiE13testInterfaceEv

             .type         _ZN4TestIiE13testInterfaceEv, @function

    _ZN4TestIiE13testInterfaceEv:

    .LFB972:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             movq        %rdi, -8(%rbp)

             popq         %rbp

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE972:

             .size _ZN4TestIiE13testInterfaceEv, .-_ZN4TestIiE13testInterfaceEv

             .globl        main

             .type         main, @function

    main:

    .LFB973:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             movl          $0, %eax

             popq         %rbp

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE973:

             .size main, .-main

             .type         _Z41__static_initialization_and_destruction_0ii, @function

    _Z41__static_initialization_and_destruction_0ii:

    .LFB974:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             subq          $16, %rsp

             movl          %edi, -4(%rbp)

             movl          %esi, -8(%rbp)

             cmpl          $1, -4(%rbp)

             jne    .L4

             cmpl          $65535, -8(%rbp)

             jne    .L4

             movl          $_ZStL8__ioinit, %edi

             call   _ZNSt8ios_base4InitC1Ev

             movl          $__dso_handle, %edx

             movl          $_ZStL8__ioinit, %esi

             movl          $_ZNSt8ios_base4InitD1Ev, %edi

             call   __cxa_atexit

    .L4:

             leave

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE974:

             .size _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii

             .type         _GLOBAL__sub_I__ZN4TestIiE13testInterfaceEv, @function

    _GLOBAL__sub_I__ZN4TestIiE13testInterfaceEv:

    .LFB975:

             .cfi_startproc

             pushq       %rbp

             .cfi_def_cfa_offset 16

             .cfi_offset 6, -16

             movq        %rsp, %rbp

             .cfi_def_cfa_register 6

             movl          $65535, %esi

             movl          $1, %edi

             call   _Z41__static_initialization_and_destruction_0ii

             popq         %rbp

             .cfi_def_cfa 7, 8

             ret

             .cfi_endproc

    .LFE975:

             .size _GLOBAL__sub_I__ZN4TestIiE13testInterfaceEv, .-_GLOBAL__sub_I__ZN4TestIiE13testInterfaceEv

             .section    .init_array,"aw"

             .align 8

             .quad        _GLOBAL__sub_I__ZN4TestIiE13testInterfaceEv

             .hidden     __dso_handle

             .ident        "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4"

             .section    .note.GNU-stack,"",@progbits

可以明显的看到,testInterface被编译进去了,所以显式特化同样伴随了实例化

好,我们介绍了这么多,那么有人要问了这玩意既然可以隐式实例化那还干嘛要显式实例化,而且是不是也没见过谁显式的实例化模板了?这事情说来话长,首先我们在使用模板的时候是否有疑惑,为啥模板的定义和实现总是都在头文件里面?这个问题说起来有点复杂,首先按照编译器的理解,当它在编译一个cpp文件的时候,用到一个类型只要是声明了,那么要么在自己里面定义,要么在别的地方定义了,所以编译器不会报错,那么编译完以后链接的时候链接器会查找所以相关的目标文件以找到需要的定义,于是我们考虑下列情况,一个头文件声明了目标接口例如template.h,实现在另一个template.cpp中,而使用在reference.cpp中,我们来看看在编译template.cpp的时候,由于没有人在该文件中实例化它所以,模板不会被编译进去,那么在reference.cpp中,由于看不到template.cpp中的定义,所以当比如用到Test<int>的时候编译器认为是在别处定义的,于是也不会去实例化它,所以最终在链接的时候链接器会说找不到Test<int>及其成员的reference,于是我们几乎都是把定义和实现放在头文件中的。那么如果想要分开可不可以呢?答案是肯定的,既然你没有隐式实例化,那好,我显式实例化总可以吧,于是,在此次显式实例化便排上用场了。我们可以在reference.cpp中显式实例化,其实在哪实例化都可以,只要保证最后编进去就行。

 

最后我们来说一下之前提出的另一个问题,单独的显式实例化一个函数到底有啥用,比如之前显式实例化了Test<int>的testInterface函数,那么其默认的构造,析构等都没有,只有这样一个成员函数有啥用呢,其实,模板的编译来看,它是在编译一个单独文件的时候按需来的,比如我在编译A.cpp的时候用到了Test<int>的testInterface,好,把他的代码编译到A.o文件去,那么在编译B.cpp的时候同样用到了testInterface,也需要将其编译到B.o里面去,那你说这不重复了吗,没错就是重复了,在链接的时候如果有重复那么就会合并,最终的target中是只有一份的。好,有了上面的背景,我们再来看这个问题,比如有A.cpp 和 B.cpp两个类,A中只是定义了一个Test<int>的对象,将其传入到B中的一个方法,该方法接受Test<int>的指针类型,如下所示,那么对B而言就不需要Test<int>的构造和析构了,那么这样B中就只需要实例化testInterface即可。


    void B::someFunc(Test<int>* t)

    {

        t->testInterface();

    }

3 示例

3.1显示特化实例

 比如,下面是一个类模板(其实是结构体,但在C++中struct与class基本相同,也是类,详见:https://blog.csdn.net/yuechuxuan/article/details/81673953

template<class T> struct PrimaryDef { 

    void f() {}  //成员函数

    void g() {}  //成员函数 

 

    static int sMem;  //静态成员

    class In{};  //内部类

 

    template<class T1> 

    void function(){}  //嵌套的函数模板

 

    template<class T1> 

class TempIn  //嵌套的类模板   

{ 

      void g(){} 

    }; 

};    // template definition  

 

上面这个类模板PrimaryDef,其中中既有成员函数,又有内部类,还有静态成员,甚至还有嵌套的类模板和函数模板。如果有一种情况,当模板参数T为int的时候,我们只需要PrimaryDef<int>有一个成员函数func,而其他任何东西都不需要,此时可以显示特化int的版本如下:


    template<> 

    class PrimaryDef<int> 

    { 

    public: 

        void func(){} 

    };  

这样一来,PrimaryDef<int>中便不再有func以外的成员PrimaryDef的其他版本的特化中也不会有func这个成员,当然我们可以给出更多的特化版本,比如可以给出PrimaryDef<char>或PrimaryDef<float>的特化版本,等等。需要注意的是:要根据实际需要来做显示特化,而没有真正需要的就交给隐式特化即可。

那么在模板中,哪些东西是可以被特化呢,从CppReference上的描述来看包括如下的内容:

  • Function template(函数模板)
  • Class template(类模板)
  • Member function of a class template(类模板的成员方法)
  • Static data member of a class template(类模板的静态数据成员)
  • Member class of a class template(类模板的成员类)
  • Member enumeration of a class template(类模板成员枚举)
  • Member class template of a class or class template(类模板或者类中嵌套的类模板)
  • Member function template of a class or class template(类模板或者类中的函数模板)

也就是说类模板和函数模板本身以外,还有很多内容是可以被特化的,我们下面举几个例子来说明一下。

   #include <iostream>

    using namespace std;

    class A{};

  

    template<class T> struct PrimaryDef {

        void f() {}

    

        void g() {}

    

        static int sMem;

    

        template<class T1>

        void func(){}

    

        template<class T1>

        class TempInner

        {

          void g(){}

        };

    }; // template definition

    

    template<typename T>

    int PrimaryDef<T>::sMem = 100;

    

    template<>

    void PrimaryDef<int>::f()

    {}

    

    template<>

    void PrimaryDef<char>::g()

    {

        cout << "invoke PrimaryDef<char>::g()" << endl;

    }

    

    template<>

    template<typename T>

    void PrimaryDef<float>::func()

    {

        cout << "invoke PrimaryDef<float>::func<T>()" << endl;

    }

    

    template<>

    template<typename T>

    class PrimaryDef<float>::TempInner{};

    

    template<>

    template<>

    void PrimaryDef<int>::TempInner<A>::g()

    {

        cout << "invoke PrimaryDef<int>::TempInner<A>::g()" << endl;

    }

在此,我们举例说明了一下如何去显示特化一个类模板中的各个成员,如果换成类成员的特化也是一样的,在此就不在一一举例了,读者可以自己写一个测试程序对其进行测试,此例中,我们首先定义了一个普通类A, 然后定义了一个PrimaryDef的类模板,之后分别特化了PrimaryDef<iint>的成员函数f,PrimaryDef<char>的成员函数g,PrimaryDef<float>的成员函数模板func,PrimaryDef<float>的成员类模板TempInner,然后特化了PrimaryDef<int>中定义的TempInner<A>的成员函数g,语法和规则都比较简单不做特别的说明,有一点要强调一下,对于内部类TempInner,或许有人会希望如下特化:

template<>

    template<typename T>

    void PrimaryDef<int>::TempInner<T>::g()

    {

        cout << "invoke PrimaryDef<int>::TempInner<T>::g()" << endl;

    }

那么上述代码是希望可以特化出PrimaryDef<int>下类模板中g的定义,那么虽然这个想法是可以理解的,但这和特化的定义是有些矛盾的,特化是要在其外部模板参数确定的情况下来完成的,而此时对g函数而言,TempInner的类型并未被确定,如此编译器会说引用了不完整类型:

Test.cpp:46:39: error: invalid use of incomplete type ‘class PrimaryDef<int>::TempInner<T1>’

 void PrimaryDef<int>::TempInner<T>::g()

                                       ^

Test.cpp:18:11: error: declaration of ‘class PrimaryDef<int>::TempInner<T1>’

     class TempInner

那么如果有此种需求,则需要特化PrimarDef<int>下整个TempInner的定义。

 

3.2全特化实例

对于函数模板而言,也可以对其做特化,比如下面的例子:

#include <iostream>

    using namespace std;

    template<typename T1, typename T2, int N>


    void tempFunc()

    {

        cout << "Primary tempFunc" << endl;

    }

    

    template<>

    void tempFunc<int, char, 110>()

    {

        cout << "Explicit Specialization tempFunc<int, char, 110>" << endl;

    }

    

    int main()

    {

        tempFunc<int, int, 1>();

        tempFunc<int, char, 110>();

    }

上例中,我们将tempFunc<int, char, 110>做了显式特化,所以在调用tempFunc<int, char, 110>()的时候就会执行该特化版本。

3.3偏特化实例

上述的这种特化方式被称为全特化,在这一点上cppReference上的描述很容易让人迷惑,我们一般来说认为全特化是显式特化的一种,但cppReference上的描述应该是explicit specialization 就是 full specialization,而除此之外我们还有一种特化方式叫偏特化(partial specialization,这种特化的方式是用来做部分特化的,也就是说当你要特化一类情况的时候或者说模板参数列表中有部分参数可以被确定的时候,但这种偏特化(部分特化)方式只能用来对类模板做特化而不能特化函数模板

 

所谓的特化一类情况,我们举一个列子:

    template<typename T>

    class A{};

如果说我们想特化的是当T是一个数组类型,我们就需要对其做特化,比如说当T为数组类型的时候我们需要对其加一个operator[]的定义:   

template<typename T>

    class A{};

    

    template<typename T, int N>

    class A<T[N]>

    {

    public:

        A(T (&array)[N])

            : mArray(array)

        {}

    

        T operator[](int i) {

            return mArray[i];

        }

    

    private:

        T (&mArray)[N];

    };

    

    int main()

    {

        int array[] = {10, 100, 99, 101, 398, 18};

        A<int[sizeof(array)/sizeof(int)]> a(array);

        for (int i = 0; i < sizeof(array)/sizeof(int); i++)

        cout << a[i] << " ";

        cout << endl;

    }

上面的例子中,当我们知道在A的模板参数是一个数组类型的时候我们希望可以对其有一个特化版本,这是一个比较典型的偏特化例子,首先我们不能完全确定要做特化的模板参数,但是呢我们知道它的部分信息,也就是说其为一个数组类型,那么我们就可以对其做特化,刚刚全特化的版本中,我们可以看出template关键字后的尖括号是空的,而偏特化的版本中template关键字后的的尖括号内是有内容的,而且可以比原始版本的参数更多但问题是在A后面的这个<>中,构成的具体类型数目和原始版本中的类型数目要相同。比如此例中,原始的版本中模板参数只有一个就是T,那么在偏特化的版本中,A后面的括号里也只可以有一个类型,就是T[N],在此T[N]是一个数组类型,而偏特化版本中的参数列表里,则有两个参数,T和N,也就是说模板参数无论有几个都可以,但最后要特化的类型数目要和原始版本相同。

 

在上面的例子中是说当我们知道要特化版本的一些信息,而还有一种情况是,我们知道了模板参数列表中有一部分参数的具体值,比如下例所示:

  template<typename T1, typename T2, int N>

    class A

    {};

    

    template<typename T>

    class A<T, char, 110>

    {

    public:

        void func()

        {

            cout << "Partial Specialization A<T, char, 110>" << endl;

        }

    };

例如,我们知道了A中参数列表中后面两个参数的具体值,而第一个参数未定时,我们可以做以上版本的偏特化。

这两种情况,有时候也会有交叉,比如下面的例子

 template<typename T1, typename T2, int N>

    class

    {};

    

    template<typename T, int N>

    class A<T[N], char, 110>

    {

    public:

        void func()

        {

            cout << "Partial Specialization A<T[N], char, 110>" << endl;

        }

    };

需要再次强调的是,偏特化只能用在类模板上,不能用在函数模板上。

偏特化的应用在实际开发中用处非常之多,比如在C++11中常用的function,我们可以看一下其定义:


    template<class> class function; /* undefined */

    template<class R, class... Args > class function<R(Args...)>;

我们可以看到原始版本是没有给出定义的,只给出了一个偏特化版本的定义,原始版本在实际开发中是没有使用到的情况的,所以没有给出定义。只要不使用就不会报错。

好了,以上要介绍的特化和实例化就到此为止了,希望能对大家有所帮助,这两个概念是模板中最为基础的概念,除了偏特化略微有些复杂其他的还是很好理解的,对于一个要把模板掌握好的人来说,这两个概念必须要非常熟悉否则后面的内容不用看了。另外说一下,之前预计5月底能弄完所有的章节,现在半个月过去好不容易写完了第一篇,实在是太忙了,估计要无限期延后了,我会尽力,望见谅。

3.4函数模板例子

函数模板使用户可以根据需要生成函数名相同,参数类型不同的函数代码。示例代码1如下:

#include <iostream>


using namespace std;


template<typename T>

T max(const T &a, const T &b, const T &c)

{

         T temp = (a>b) ? a : b;

         return (temp>c) ? temp : c;

}

int main(int argc, char *argv[])

{

         int a = 10;

         int b = 22;

         int c = 15;


         cout << max('a', 'b', 'c') << endl;

         cout << max(a, b, c) << endl;

         cout << max(55.55, 11.11, 44.44) << endl;


         getchar();

         return 0;

}

运行结果

可以看出,“max('a', 'b', 'c')”、“ max(a, b, c)”、“ max(55.55, 11.11, 44.44)”参数类型不同,却不用编写三个函数,这就是函数模板的优势。

示例代码2如下:

#include <iostream>

using namespace std;

template <class T>


void f(T a[], int n)

{

         T t = 0;

         for (int i = 0; i < n / 2; i++)

         {

                   t = a[i]; a[i] = a[n - 1 - i]; a[n - 1 - i] = t;

         }

}

int main()

{

         int a[5] = { 10,21,34,4,50 };

         double d[6] = { 11.1,10.1,9.1,8.1,7.1,6.1 };

         f(a, 5);

         f(d, 6);

         for (int i = 0; i < 5; i++)

                   cout << a[i] << "  ";

         cout << endl;

         for (int i = 0; i < 6; i++)

                   cout << d[i] << "  ";

         cout << endl;

         system("pause");

         return 0;

}

 

运行结果:

函数模板的作用将不同数据类型的数组进行了转置。

3.5 类模板实例

#include <iostream>

using namespace std;

template <class T>

//定义类模板

class Compare

{

private:

         T x, y;

public:

         Compare(T a, T b)

         {

                   x = a; y = b;

         }

         T max()

         {

                   return (x>y) ? x : y;

         }

};

int main()

{

         Compare<int > cmp(1, 2);  //用类对象Compare<int》初始化对象cmp(1,2),用于两个整数的比较

         cout << cmp.max() << endl;

         Compare<double> cmp_2(1.1, 2.2);

         cout << cmp_2.max() << endl;

         Compare<string> cmp_3("abc", "bbb");

         //cout << cmp_3.max() << endl;


         getchar();

         return 0;

}

结果

 

4 总结

模板的作用其实与函数的重载差不多,只不过模板的范围更广不只是函数,还包括类、成员变量等多种

5 主要参考资料

https://blog.csdn.net/harman_zjc/article/details/70477650

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值