C++ 中的模板类声明头文件和实现文件分离后,如何能实现正常编译?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 中的模板类声明头文件和实现文件分离后,如何能实现正常编译?相关的知识,希望对你有一定的参考价值。

貌似想写一个模板类的库,隐藏实现是不可能的?

参考技术A #include "template.h"
#include "template.cpp"
可以分离。但应该不算隐藏。。。。
参考技术B 说了一大堆都怎么不说关键点呢?模板的本质和宏差不多,就是一系统预定义,不能把它和普通的源文件混淆,所以理所应当的模板实现应该以宏定义一样对待写在头文件里。写在源文件里什么鬼?

C++模板进阶

模板进阶

目标

非类型模板参数
类模板的特化
类模板特化的应用之类型萃取
模板的分离编译

非类型模板参数

模板参数分类类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
注意:
1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。

2. 非类型的模板参数必须在编译期就能确认结果

模板的特化

概念

通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果
这时候就需要对模板进行特化,即在原模板的基础上,针对特殊类型所进行特殊化的实现方式,模板特化中分为函数模板特化与类模板特化

模板的编译

函数模板的特化

函数模板特化步骤:
1.必须先有一个基础的函数模板
2.关键字template后面接一对空的尖括号
3.函数名后跟一对尖括号,尖括号中指定需要特化的类型
4.函数形参列表:必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误
由于编译器可以自动推演,选择最合适的函数接口,即有适合的函数,就会直接调用,而不会再去进行推演因此函数模板特化的时候,可以写成不严谨形式的特化
非严谨形式的特化

template<class T>
int compare( T& a,  T& b)

	cout << "template<T>调用" << endl;
	return a > b;

//非严谨的函数模板化
int  compare( char* str1, char* str2)

	return strcmp(str1,str2);

int main()

	char  str1[] = "hello";
	char str2[] = "world";
	int ret = compare(str1, str2);
	if (ret > 0)
	
		cout << "str1>str2" << endl;
	
	else
	
		cout << "str1<=str2" << endl;
	


严谨的函数模板特化

template<class T>
int compare(T a, T b)

	cout << "template<T>调用" << endl;
	return a > b;

//严谨的函数模板化
template<>
int  compare<char*>(char* left, char* right)

	if (strcmp(left, right) > 0)
		return 1;
	else if (strcmp(left, right) == 0)
	
		return 0;
	
	else
	
		return -1;
	


int main()

	char  str1[] = "hello";
	char str2[] = "world";
	int ret = compare(str1, str2);
	if (ret > 0)
	
		cout << "str1>str2" << endl;
	
	else
	
		cout << "str1<=str2" << endl;
	

类模板化

全特化

将模板参数列表中所有的参数都确定化

template<class T1, class T2>
class Data

public:
	Data()
	
		cout << "Data<T1, T2>" << endl;
	

private:
	T1 _d1;
	T2 _d2;
;

// 类模板的特化方式一:全特化
template<>
class Data<int, double>

public:
	Data()
	
		cout << "Data<int, double>" << endl;
	

private:
	int _d1;
	double _d2;
;

int main()

	Data<int, int> d1;
	Data<double, double> d2;
	Data<int, double> d3;
	Data<double, int> d4;
	return 0;

偏特化–重点

偏特化方式一、部分特化

template<class T1, class T2>
class Data

public:
	Data()
	
		cout << "Data<T1, T2>" << endl;
	

private:
	T1 _d1;
	T2 _d2;
;

// 偏特化方式一:部分特化
// 将来对Data类模板进行实例化时,第二个参数如果是int类型的,都走特化版本
template<class T1>
class Data<T1, int>

public:
	Data()
	
		cout << "Data<T1, int>" << endl;
	

private:
	T1 _d1;
	int _d2;
;

// 偏特化方式二:对参数类型更详细的限制
// 说明:在对Data实例化时,只要两个参数类型都是指针参数,就使用特化版本
// 否则就使用类模板

template<class T1, class T2>
class Data<T1*, T2*>

public:
	Data()
	
		cout << "Data<T1*, T2*>" << endl;
	

private:
	T1* _d1;
	T2* _d2;
;

模板分离编译

什么是分离编译

一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式

模板分离所带来的优点

1.提高效率

2.方便管理和阅读代码
如上图所示,一个整体的文件,里面充满了内容,没有分出模块,阅读起来和管理起来都是比较麻烦的
当文件分为一个个小模块之后,阅读感更好,管理也比较方便

普通.h文件实例化模板所造成问题


答案看这里

解决方法

首先介绍一下.hpp与.h区别

hpp,其实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要include该hpp文件即可,无需再将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,采用hpp将大幅度减少调用 project中的cpp文件数与编译次数,也不用再发布烦人的lib与dll,因此非常适合用来编写公用的开源库。
链接

解决方案–将头文件由.hpp替换掉.h

#pragma once
template<class T>
T ADD(T a, T b)

	return a + b;

#include"template.hpp"
int main()

	cout << ADD(1, 2) << endl;
	cout << ADD(2.0, 10.0) << endl;

模板优缺点

优点】

  1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
  2. 增强了代码的灵活性

【缺陷】

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误

以上是关于C++ 中的模板类声明头文件和实现文件分离后,如何能实现正常编译?的主要内容,如果未能解决你的问题,请参考以下文章

线性表-顺序表链表类模板的实现(数据结构基础 第2周)

(C++)模板分离编译面对的问题

C++中,怎么将函数模板的声明和定义分开写?

C++模板分离

将类代码分离为头文件和 cpp 文件

C++模板进阶