C++模版:包含模型、显式实例化、分离模型

函数和类类型声明和定义的实质

非模板类类型的分文件定义

test.cpp:

#include "test.h"
#include <iostream>
using namespace std;  test::test()
{  cout << "调用默认构造函数" << endl;
}  test::~test()
{  cout << "调用析构函数" << endl;
}  

test.h

#pragma once
#include <iostream>
using namespace std;  class test
{
private:  int data;
public:  test();  ~test();
};  

main.cpp

#include "test.h"
#include <iostream>
using namespace std;  int main()
{  test test01;
}  

以上是非模板的类类型的声明,实现和使用,其实上述的实现原理如下:

注意:

这里的class A的声明与实现由于在不同文件因此属于不同的编译单元,但是由于这两个编译单元都存在且可以通过声明文件中的地址找得到另一个编译单元中的函数实现体,因此,我们可以最终实现函数调用。

模板的类类型分文件定义

test.cpp

#include "test.h"
#include <iostream>
using namespace std;  template <typename T>
test<T>::test()
{  cout << "调用默认构造函数" << endl;
}  template <typename T>
test<T>::~test()
{  cout << "调用析构函数" << endl;
}  

test.h

#pragma once
#include <iostream>
using namespace std;  template <typename T>
class test
{
private:  T data;
public:  test();  ~test();
};  

main.cpp

#include "test.h"
#include <iostream>
using namespace std;  int main()
{  test<int> test01;
} 

异常错误:

异常分析:

对于“模板类型的类类型和函数”而言,在定义实体之前,一切都是未知数,因此函数体无法申请到指定大小的内存,同样,类类型中的所有成员也无法申请到指定大小的内存。但当实例化后,由于main.cpp中只包含了test.h模板类类型的头文件并没包含模板函数的实现体.cpp文件,因此我们在main函数中指定模板参数只会实例化test.h文件中的那一部分,真正有用的模板函数并没有被实例化,也就是说我们无法通过.h文件找到.cpp中类类型成员函数的实现。

异常原理如下:

包含模型

针对上述无法同时实例化.h文件与.cpp文件内容的问题,我们进行如下修正:

① 同时包含与模板类定义有关的.cpp文件与.h文件

#include "test.h"
#include "test.cpp"
#include <iostream>
using namespace std;  int main()
{  test<int> test01;
}  

这样的话程序就正常了:

上述程序也可以这样写:将模板的实现文件.cpp包含进模板的声明文件.h中

test.cpp

#pragma once
#include <iostream>
using namespace std;  template <typename T>
class test
{
private:  T data;
public:  test();  ~test();
};  template <typename T>
test<T>::test()
{  cout << "调用默认构造函数" << endl;
}  template <typename T>
test<T>::~test()
{  cout << "调用析构函数" << endl;
}  

test.h

#pragma once
#include <iostream>
using namespace std;  template <typename T>
class test
{
private:  T data;
public:  test();  ~test();
};  #include "test.cpp"  // 包含模板的实现

main.cpp

#include "test.h"
#include <iostream>
using namespace std;  int main()
{  test<int> test01;
} 

​​​​​​​

这样做,我们的test.h文件中就可以完美的呈现test模板类的成员结构,代码可读性提高。

② 在main.cpp中同时包含模板类的.h和.cpp文件未免太过杂乱,我们将模板类放在一个文件中实现不好吗?这样的话实现步骤就变成了:

如果将模板类的.h声明文件和.cpp实现文件结合在一起组成一个.hpp/.h文件的话,就没有链接器的事了,直接编译器就可以将其实例化。

test.hpp

#pragma once
#include <iostream>
using namespace std;  template <typename T>
class test
{
private:  T data;
public:  test();  ~test();
};
template <typename T>
test<T>::test()
{  cout << "调用默认构造函数" << endl;
}  template <typename T>
test<T>::~test()
{  cout << "调用析构函数" << endl;
}  

main.cpp

#include "test.hpp"
#include <iostream>
using namespace std;  int main()
{  test<int> test01;
}  

这样就构成了我们的包含模型,但是这样的会带来一些性能上的丢失。首先,我们先要了解为什么类体或大型函数体的实现一般采用分文件编写方式?

① 编译器要达到极致的性能,就要尽量减编译后代码的长度,有以下方式会使得编译器编译出的代码过于冗余:

⑴ 将长度较长的函数实现体声明为内联函数:

⑵ 使用迭代次数过多的递归函数:

⑶ 当在一个项目中包含重复实现的代码,编译器为了避免“代码膨胀“会对其进行优化,这个优化也需要一定的时间:

因此,我们看到当我们将模板的声明&实现全部放在.hpp文件中,当其他文件也是用这个文件中的模板进行实例化时,编译器会进行与优化,但是由于我们把全部的东西(类模板的声明和定义)放在了这个文件中,导致编译器优化那么大段重复代码的时间较长,效率变低了。

显式实例化

test.h(代码不变):

#pragma once
#include <iostream>
using namespace std;  template <typename T>
class test
{
private:  T data;
public:  test();  ~test();
};  

我们只需在模板类的实现文件.cpp文件中添加我们需要实例化的有限个类型的实例化模板。

test.cpp

#include "test.h"
#include <iostream>
using namespace std;  template class test<int>;  // 新添加的地方
template <typename T>
test<T>::test()
{  cout << "调用默认构造函数" << endl;
}  template <typename T>
test<T>::~test()
{  cout << "调用析构函数" << endl;
}  

这里,我们一定要注意实例化模板的形式:template+类类型实体。

main.cpp

#include "test.h"
#include <iostream>
using namespace std;  int main()
{  test<int> test01;  // test<double> test02;  是不可以的
}  

由于我们仅仅实例化了int类型的test模板类,因此我们无法使用除int类型以外的其他实例化的模板类。

这样的优点:

⑴ 编译器不用再优化切除项目中重复的代码,因为由于我在模板类实现文件中已经定义了一个使用int实例化的模板类,因此当其他文件使用int类型的模板类时,不用再额外定义了是直接使用我们定义好的就OK了,这样做避免了同一个模板类在多个文件(编译单元)中重复定义。

这样的缺点:

⑴ 我们发现,我们只能使用int类型的模板类,我们使用其它类型的模板类时就会发生“链接器错误“。因此,显式实例化适合于”模板类实例化的方向只有有限种,比如这个类模板只有int,double两种类型的“;

⑵ 我们编写大型的程序,针对一个模板我们不可能知道我们要使用的模板参数的全部可能的情况。

显式实例化的原理如下:

分离模型

函数模版的编译模式分两种:完全包含编译模式和局部编译模式(需要用export关键字),不同的编译器对这两种编译模式的支持各不相同,但一般都支持完全包含编译模式,具体支持情况需要参照具体使用的编译器文档。(VS2017不支持export关键字)

为了避免完全编译模式的这种低效率,出现了局部编译模式。编译器会自动跟踪到类模板中成员函数的定义(通过export关键字)这样就提高了编译速度。

在模板定义.cpp文件中使用export的形式如下:

#include "test.h"
#include <iostream>
using namespace std;  export  // 使用后,该文件中其他模板都带有export类型
template <typename T>
test<T>::test()  // 非内联函数
{  cout << "调用默认构造函数" << endl;
}  template <typename T>
test<T>::~test()  // 非内联函数
{  cout << "调用析构函数" << endl;
}  

test.h(不变):

#pragma once
#include <iostream>
using namespace std;  template <typename T>
class test
{
private:  T data;
public:  test();  ~test();
};  

main.cpp(调用方式不变):

#include "test.h"
#include <iostream>
using namespace std;  int main()
{  test<int> test01;
}  

注意:模板中的全部成员函数不可以声明为inline类型(内联函数)。

类类型中的内联函数包括:

① 在类内定义的成员函数:

template <typename T>
class test
{
private:  T data;
public:  test(); // 类内的内联函数  (默认作为内联函数){  cout << "调用默认构造函数" << endl;  }  ~test(); // 类外的内联函数
};  template <typename T>
inline test<T>::~test()
{  cout << "调用析构函数" << endl;
}  

② 类外的内联函数:

template <typename T>
class test
{
private:  T data;
public:  test(); // 类内的内联函数  {  cout << "调用默认构造函数" << endl;  }  ~test(); // 类外的内联函数
};  template <typename T>
inline test<T>::~test()  // 类外内联函数必须添加inline关键字
{  cout << "调用析构函数" << endl;
}  

以上两种内联函数模式都不可以使用export关键字进行局部编译。

为什么要要求export不可以和inline共存呢?

export的作用是“将模板类声明(.h文件内内容)部分的实例化工作交给编译器完成,然后将模板类的实现全部放在另一个编译单元内(.cpp文件),并且在两个编译单元之间建立沟通的桥梁“。一旦使用inline内联函数,模板类的成员函数实现体就不可能完全分离出来,就会嵌在编译器中一部分。这违背了”分离模型“的实质。

C++模版:包含模型、显式实例化、分离模型(详解)相关推荐

  1. (转)c++模版:包含模型、显式实例化、分离模型

    c++模版:包含模型.显式实例化.分离模型 大多数c和c++程序员会这样的组织他们的非模板代码:类和其他类型放在头文件中,对于全局变量和(非内联)函数,只有声明放在头文件中,定义则位于.cpp文件中, ...

  2. boost::container实现显式实例化平面集测试程序

    boost::container模块实现显式实例化平面集的测试程序 实现功能 C++实现代码 实现功能 boost::container模块实现显式实例化平面集的测试程序 C++实现代码 #inclu ...

  3. boost::container实现显式实例化列表的测试程序

    boost::container模块实现显式实例化列表的测试程序 实现功能 C++实现代码 实现功能 boost::container模块实现显式实例化列表的测试程序 C++实现代码 #include ...

  4. 数仓-维度模型之维度迟到问题处理详解

    数仓-维度模型之维度迟到问题处理详解 摘要:在数据仓库项目中,从贴源层(ODS)更新到数据仓库层(DW)时,出现了拉链形式的维表数据更新不及时的情况,从而导致事实表中的该维度列值为空或旧值.需要根据维 ...

  5. java poi 模板填数据库,java使用POI读取excel模版并向固定表格里填写数据详解

    java使用POI读取excel模版并向固定表格里填写数据详解:public class ExportExcelDemo { private HSSFWorkbook workbook = null; ...

  6. Tribon模型数据抽取之sx700.exe详解

    Tribon模型数据抽取之sx700.exe详解 一:简介 网络上关于Tribon模型数据抽取的论文最早流传的是2006哈尔滨工程大学姚竞争的工学硕士学位论文<TRIBON模型的数据抽取及二次开 ...

  7. 4阶显式Runge-Kutta法解常微分方程的通用程序--python实现

    对于常微分方程,RK方法速度快,精度高,代码简单,是最为实用的数值方法之一.RK方法很简单,类似梯形法,RK法也是根据前一步点的值推算后一步点.具体算法见以下链接 https://wenku.baid ...

  8. 使用 RNN 模型从零实现 情感分类(详解)

    文章目录 说明 思路 Step1:读取数据集 Step2:生成 tokens 数组 Step3:使用 Word2Vec 生成词向量 Step4:将 tokens 内的词语转化为向量索引 Step5:生 ...

  9. Webflux系列之反应式编程核心基础详解

    常用官网学习地址 反应编程Reatvie programmming :   https://projectreactor.io/docs/core/release/reference/ webflux ...

最新文章

  1. SpringBoot 自动解析参数:HandlerMethodArgumentResolver
  2. [专栏目录]-ATF/FF-A/specification学习 -- ongoing
  3. 技术分享:如何避免ajax重复请求?
  4. 视图中的难点:主键表 About Key-Preserved Tables
  5. 【配置文件】Log4j
  6. ZH奶酪:【阅读笔记】Deep Learning, NLP, and Representations
  7. python怎么定义名称_Python模块的定义,模块的导入,__name__用法实例分析
  8. sql设为简单模式sql_SQL模式演练
  9. Lua学习笔记3. 函数可变参数和运算符、转义字符串、数组
  10. Ext.Net学习笔记20:Ext.Net FormPanel 复杂用法
  11. python-levenshtein —— 字符串相似度的计算
  12. HighCharts:隐藏最下方logo
  13. 思科6000系列交换机配置维护手册
  14. HC-SR04超声波传感器
  15. 渥太华计算机读研的好学校,加拿大硕士留学:渥太华最好的大学硕士项目
  16. 华为鸿蒙11公测版,首升鸿蒙2.0系统!华为官宣这10款机型率先公测EMUI11-互联网/电商-文章-小虾米...
  17. NVivo更改背景字体
  18. LTE连接态读取系统消息SIB24疑问
  19. c语言单片机温度调节系统设计,基于单片机的温度控制系统的设计
  20. Snowboy 声音识别

热门文章

  1. ubuntu安装最新版谷歌浏览器
  2. 论坛私信营销的正确操作姿势
  3. floyd算法java_利用JAVA和Floyd算法实现上海地铁最短路线搜索系统
  4. 华为校招机试 - 发广播(Java JS Python)
  5. D2R:将关系数据库映射到RDF
  6. 支付宝集成过程详解-android
  7. [FROM WOJ]#2235 Maintain
  8. Instantiation of utility class ‘类名‘
  9. Unity3D 场景切换异步加载进度
  10. 全民上云季,云服务器有哪些优势和特点?