C++11模板隐式实例化、显式实例化声明、定义(简单易懂)

1. 隐式实例化

在代码中实际使用模板类构造对象或者调用模板函数时,编译器会根据调用者传给模板的实参进行模板类型推导然后对模板进行实例化,此过程中的实例化即是隐式实例化。

template<typename T>
T add(T t1, T2)
{
    return t1 + t2;
}

template<typename T>
class Dylan
{
public:
    T m_data;
};

int main()
{
    int ret = add(3,4);//隐式实例化,int add<int>(int t1, int t2);
    Dylan<double> dylan;//隐式实例化
}

2. 显式实例化声明、定义

extern template int add<int>(int t1, int t2);//显式实例化声明
extern template class Dylan<int>;            //显式实例化声明

template int add<int>(int t1, int t2);       //显式实例化定义
template class Dylan<int>;                   //显式实例化定义

当编译器遇到显式实例化声明时,表示实例化定义在程序的其他地方(相对于当前cpp文件)即在其他某一个cpp文件中定义,因此不再按照模板进行类型推导去生成隐式实例化定义。

当编译器遇到显式实例化定义时,根据定义所提供的模板实参去实例化模板,生成针对该模板实参的实例化定义。

3. 显式实例化的用途

模板类、函数通常定义在头文件中,这些头文件会被很多cpp文件包含,在这些cpp文件中会多次使用这些模板,比如下面的例子:

//template.hpp
template<typename T>
class Dylan
{
public:
    T m_data;
};

//test1.cpp
#include "template.hpp"
Dylan<int> t1;
Dylan<int> t2;

//test2.cpp
#include "template.hpp"
Dylan<int> t3;
Dylan<int> t4;

在test1.cpp/test2.cpp 中多次实例化了Dylan<int>类,按说编译完后的可执行程序中会包含多份Dylan<T>的定义,然而实际上,整个程序中却只有一份Dylan<T>的定义。这个处理是在编译和链接过程中实现的,目前主流的实现模式有两种:      

a. Borland模式

Borland模式通过在编译器中加入与公共块等效的代码来解决模板实例化问题。在编译时,每个文件独立编译,遇到模板或者模板的实例化都不加选择地直接编译。在链接的时候将所有目标文件中的模板定义和实例化都收集起来,根据需要只保留一个。这种方法实现简单,但因为模板代码被重复编译,增加了编译时间。在这种模式下,我们编写代码应该尽量让模板的所有定义都放入头文件中,以确保模板能够被顺利地实例化。要支持此模式,编译器厂商必须更换支持此模式的链接器。

b. Cfront模式

AT&T编译器支持此模式,每个文件编译时,如果遇到模板定义和实例化都不直接编译,而是将其存储在模板存储库中(template repository)。模板存储库是一个自动维护的存储模板实例的地方。在链接时,链接器再根据实际需要编译出模板的实例化代码。这种方法效率高,但实现复杂。在这种模式下,我们应该尽量将非内联成员模板的定义分离到一个单独的文件中,进行单独编译。

在一个链接器支持Borland模式的编译目标(编译后的可执行文件)上,g++使用Borland模式解决实例化问题。比如ELF(Linux/GNU), Mac OS X, Microsoft windows, 否则,g++不支持上述两种模式。

如何避免Borland模式的缺点?

上面我们说g++实现的是Borland 模式,由于我们为每一份实例化生成代码,这样在大型程序中就有可能包含很多重复的实例化定义代码,虽然链接阶段,链接器会剔除这些重复的定义,但仍然会导致编译过程中的目标文件(或者共享库文件)过于庞大。这时候,我们就可以通过C++11的模板显式实例化的方法解决。看下面的代码:

// template.hpp
template<typename T>
class Dylan
{
public:
    Dylan(T t);
    T m_data;
};

// template.cpp
#include "template.hpp"
template<typename T>
Dylan<T>::Dylan(T t)
{
    m_data = t;
}

template class Dylan<int>; //模板实例化定义

// main.cpp
#include "template.hpp"
extern template class Dylan<int>; //模板实例化声明,告诉编译器,此实例化在其他文件中定义
                                  //不需要根据模板定义生成实例化代码  
int main()
{
    Dylan<int> dylan(3);//OK, 使用在template.cpp中的定义
    Dylan<float> dylan1(3.0);//error, 引发未定义错误
}

   上面的代码中,我们将模板类的具体定义放在template.cpp中,并且在template.cpp中通过显式实例化定义语句具体化了Dylan<int>。在main.cpp中,我们通过显式实例化声明告诉编译器,Dylan<int>将在其他文件中定义,不需要在本文件中根据template.hpp的类模板实例化Dylan<int>。

由于我们没有针对Dylan<float>做显式实例化的声明和定义,因此Dylan<float> dylan(3.0)会根据template.hpp中的类模板定义进行隐式实例化,然而构造函数是在template.cpp文件中定义的,在template.hpp中找不到构造函数的定义,因而报错。如果把构造函数的定义挪回template.hpp,那Dylan<float>就能通过编译了。

Note:在编译中,如果指定-fno-implicit-templates,编译器就会禁止隐式实例化,从而只使用显式实例化。

参考文献:Template Instantiation (Using the GNU Compiler Collection (GCC))

   


 

  • 3
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
函数模板可以通过以下三种方式进行实例化: 1. 隐式实例化:当我们调用一个函数模板时,编译器会根据实际的参数型推导出模板参数型,并自动实例化出具体的函数。这个过程称为隐式实例化,例如: ``` template <class T> void Swap(T& a, T& b) { T temp = a; a = b; b = temp; } int main() { int a = 1, b = 2; Swap(a, b); // 隐式实例化出 Swap<int>(int&, int&) return 0; } ``` 2. 显式实例化:我们可以通过在模板定义之后显式声明模板参数型,来要求编译器实例化出特定的函数。这个过程称为显式实例化,例如: ``` template <class T> void Swap(T& a, T& b) { T temp = a; a = b; b = temp; } // 显式实例化出 Swap<int>(int&, int&) template void Swap<int>(int&, int&); int main() { int a = 1, b = 2; Swap(a, b); // 隐式实例化出 Swap<int>(int&, int&) return 0; } ``` 3. 显式具体化:我们也可以通过特化函数模板来实现显式具体化,即针对特定的型提供特定的函数实现。这个过程称为显式具体化,例如: ``` template <class T> void Swap(T& a, T& b) { T temp = a; a = b; b = temp; } // 显式具体化出 Swap<int>(int&, int&) template <> void Swap<int>(int& a, int& b) { int temp = a; a = b; b = temp; std::cout << "Swapping integers!" << std::endl; } int main() { int a = 1, b = 2; Swap(a, b); // 显式具体化出 Swap<int>(int&, int&) return 0; } ``` 需要注意的是,显式具体化并不会自动推导型,因此需要手动指定模板参数型。另外,显式具体化的优先级高于隐式实例化显式实例化,因此在调用时会优先选择显式具体化的函数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值