C++ 模板学习总结(二)基本概念、实例化和具体化

前提:

前面已经将此次要总结的内容基本列出来了,希望可以全面总结一下模板的内容,为大家进行一次模板知识扫盲,本次要阐述的内容为模板的基本概念,说明模板到底是什么,有什么用,以及什么叫模板的实例化和具体化(具体化也被称为特化)。要特别说明的是本次说明最高只限于C++11的内容,更新的标准我也没有学习过。

模板:

首先要明白到底什么是模板,在CppReference上的定义如下:

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

意为模板是定义了一族类或者是函数或者是别名(C++11)的实体,至于他们以嵌套类或成员函数的形式出现就已经是后话了,最重要的是模板对一族定义,那么也就是说每一个有相同模板生成的类或函数各自间毫无实质关系,唯一的关系就是被同一个模板来生成的,而模板本身并不能在程序运行中产生任何影响,举个实际的例子,模板就像我们要生产一个东西的模具一样,但模具本身和最终生产的东西并没有关系。

比如以下模板:
template<typename T>  
class TestTemp{};  
可能是最为简单的一个模板类了,那么当我们在使用它的时候我们必须对其指定一个具体的类型,让其产生一个真正可用的类方能使用,但是我们永远不能直接使用TestTemp来进行创建对象这样的具体工作。例如
TestTemp<int> a;  
TestTemp<char> b;  

我们使用int和char分别生成了两个具体的类TestTemp<int>和TestTemp<char>。这两个类都是由TestTemp这个模板生成出来的,但是两者之间毫无关系,既不是相同类型也没有任何继承关系。
在CppReference上有这样一句话:

A class template by itselfis not a type, or an object, or any other entity.

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

模板的特化:

很多人可能认为我说的特化是显示特化,但在此我要强调一下,这里的特化是泛指的,有些教材和书籍上所说的具体化实际上就是特化,原词为Specialization可能是各自翻译上的差异。
那么什么叫特化呢,实际上就是根据模板实参来生成相应的、具体的类或者函数。换句话说,当使用具体的模板实参来替换模板参数列表里面的每一个形参后生成的类或函数的代码,就是对应这个模板实参的特化。
比如上面的TestTemp<int>,那么此时就会使用int来替换类模板中的T,当替换后会生成一个针对于int这个模板实参的类定义,那么这个类定义便是TestTemp在int上的特化。

对于类模板而言,在使用的时候所有的模板参数都是必须要指定的,比如说你必须通过TestTemp<int>这样的方式明确指定出模板实参为int,但是函数模板就有些不同了,当调用是函数模板会根据传入的函数参数(Function Argument)推导出模板实参(Template Argument),当然了传入的参数必须是可推导的,这个我们后期再说,当函数模板推导出模板实参后,可以再根据推导结果做模板特化,同样,函数模板也可以显示的指定模板参数。

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

对于函数模板而言,也可以对其做特化,比如下面的例子:
#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>()的时候就会执行该特化版本。

上述的这种特化方式被称为全特化,在这一点上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...)>;
我们可以看到原始版本是没有给出定义的,只给出了一个偏特化版本的定义,原始版本在实际开发中是没有使用到的情况的,所以没有给出定义。只要不使用就不会报错。

以上就是我们要介绍的特化。
那么在模板的基本概念中除了特化之外,还有一个重要的概念是实例化(Instantiation),在概念上来讲我们之前说过一个类模板本身既不是一个类型也不是一个对象,可以说它什么实体都不是,必须要在其实例化之后才能真正的使用它,那么实例化的概念就是提供模板的全部参数,编译器便会根据所提供的参数来生成具体的代码,如此以后才可以使用,这个概念和之前的特化有点相似但也有些不同,特化的意思是根据模板的参数来确定具体的代码,无论是显示特化还是隐式特化,总之它会生成或者找到需要使用的代码,而具体化的过程是说要把代码编译进去,我不知道我是否有解释清楚,下面会先介绍一下具体化的概念和方式,最后会使用一个具体的例子来说明具体化的定义。

那么我们上面介绍了实例化的基本概念,所谓的实例化就是说把对应的模板特化的代码真正的编译进去,那么和特化相似,实例化也包括了隐式和显式两种,先看一下显式实例化的方法:

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

好以上我们介绍了显式实例化,那么我们平时在使用一个类模板的时候很少用过这样的方法,为什么代码也被编译进去了呢?答案是还存在另一种实例化的方式--隐式实例化,那么对于隐式实例化的介绍我直接贴上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();
}
好了,以上要介绍的特化和实例化就到此为止了,希望能对大家有所帮助,这两个概念是模板中最为基础的概念,除了偏特化略微有些复杂其他的还是很好理解的,对于一个要把模板掌握好的人来说,这两个概念必须要非常熟悉否则后面的内容不用看了。另外说一下,之前预计5月底能弄完所有的章节,现在半个月过去好不容易写完了第一篇,实在是太忙了,估计要无限期延后了,我会尽力,望见谅。
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值