C++ - 模板分离编译

模板分离编译

我们先来看一个问题,我们用 stack 容器的声明定义分离的例子来引出这个问题:

// stack.h

cpp 复制代码
// stack.h
#pragma once
#include<deque>

namespace My_stack
{
	template<class T, class Container = std::deque<T>>
	class stack
	{
	public:

		void push(const T& x)
		{
			_con.push_back(x);
		}

		void pop()
		{
	    	_con.pop_back ();
		}

		T top()
		{
			return _con.back();
		}

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};
}

现有如上这个stack 类,我们把 push ()函数 和 pop()函数声明定义分离,如下所示:

// stack.cpp

cpp 复制代码
#include"stack.h"

namespace My_stack
{
	template<class T, class Container>
	void stack<T, Container>::push(const T& x)
	{
		_con.push_back(x);
	}

	template<class T, class Container>
	void stack<T, Container>::pop()
	{
		_con.pop_back();
	}
}

看似上述的分离是没有问题,但是,当我们编译的时候就报错了:

上述报了一些 link 链接错误,这时候我们就很疑惑,反复查看声明和定义的链接关系,也看不出问题。

我们调用 size ()这些没有进行声明定义分离的函数是没有 问题,问题就出在,我们什么定义分离的 push()和 pop()。

我们在简单定义一个 A 类来对比:

cpp 复制代码
// stack.h

namespace My_stack
{
    class A
	{
	public:
		void fun1();
		void fun2();
	};
}


// stack.cpp

namespace My_stack
{
    void func1()  // 定义了
	{}

    // 未定义
	// void func2()
}
	

在从源文件生成 可执行文件,有以下几步:

上述的 push()函数 和 func2 ()函数都编译通过了,因为声明只是一个承诺,编译器在编译的时候,只会看函数有没有声明,如果这个函数有定义,那么就会去看这个函数的定义是否和声明一致,一致那么就编译通过了;没有定义有声明也是可以编译通过的。

但是在最后链接的时候:

  • fun1()函数有声明和定义,成功链接上了;
  • 而func2()有声明但是没有定义,所以没有连接上(这是正常的);
  • 但是此时的问题是 push()函数有声明和定义,但是却链接失败了。

简单形容就是,我想买手机像func1()这个朋友借钱,它有声明,也就给我承诺了借我1000元,在最后买手机的时候,func1()也借给了我1000元。

而 func2()就是有声明,承诺给我500元,但是在买手机那一天,他却说它家里面有事,有需要用钱就没借我钱。

最后是 push()他也有声明,也承诺借钱给我,买手机那一天也确实向银行转了钱,但是我却没有收到。

最后push()的问题不是出在 我 和 push()身上,而是出在银行身上。

所以,现在你应该明白这个问题出来哪一个身上了,没错就是 编译器的问题。

问题就出在编译的时候,因为地址是存在编译生成的 .s 文件当中,而在声明当中给的模版参数是 T,编译器在编译的时候不知道 这个 T 是什么类型,没错,就是出在了没有实例化上面。编译器都不知道实例化出的 T 的类型是什么,就无法生成这个函数的地址。

func1()可以生成地址是因为 func1()不是模版函数,而push()是模版参数,只有实例化之后才能生成地址。

解决方式

第一种是显示实例化:

cpp 复制代码
namespace My_stack
{
	template<class T, class Container>
	void stack<T, Container>::push(const T& x)
	{
		_con.push_back(x);
	}

	template<class T, class Container>
	void stack<T, Container>::pop()
	{
		_con.pop_back();
	}

	template
	class stack<int>;
}

这里的 template 是语法规定,告诉编译器这里是 显示实例化。

但是这个方式只能适用于单个类型,如果是其他类型的模版参数就不行了。

我们反观 top()这些函数,没有进行声明定义分离的函数,之所以能找到是因为这些函数的地址不需要再下面进行寻找,编译的时候就已经找到了地址(有定义就实例化,自然就找到了地址)。

而其他函数在后面需要找是因为,push()这些函数只有声明。

所以,如果我们想要进行声明定义分离的话,模版的分离不能分在两个问题,因为把声明和定义分离在同一个文件当中(如下代码所示):

cpp 复制代码
#pragma once
#include<deque>

namespace My_stack
{
	template<class T, class Container = std::deque<T>>
	class stack
	{
	public:

		void push(const T& x);
		void pop();

		//void push(const T& x)
		//{
		//	_con.push_back(x);
		//}

		//void pop()
		//{
		//	_con.pop_back ();
		//}

		T top()
		{
			return _con.back();
		}

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

	template<class T, class Container>
	void stack<T, Container>::push(const T& x)
	{
		_con.push_back(x);
	}

	template<class T, class Container>
	void stack<T, Container>::pop()
	{
		_con.pop_back();
	}
}

在库当中也是这样,把声明和定义,分离在同一个文件当中。

当然,为了实现像之前一样的,用 .cpp 和 .h 两个文件实现的声明和定义分离的效果,我们可以将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的 。推荐使用这种
这里的 .hpp 表示的意思就是 .cpp 和 .h 的合体。当然不写成 .hpp 也是可以的。

模版的优缺点

【优点】

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

【缺陷】

  1. 模板会导致代码膨胀问题,也会导致编译时间变长
  2. 现模板编译错误时,错误信息非常凌乱,不易定位错误
相关推荐
刺客xs6 分钟前
Qt------信号槽,属性,对象树
开发语言·qt·命令模式
2501_921649498 分钟前
免费获取股票历史行情与分时K线数据 API
开发语言·后端·python·金融·数据分析
乐观甜甜圈9 分钟前
JDK8 中线程实现方法与底层逻辑详解
java
尤物程序猿13 分钟前
Java如何不建表完成各种复杂的映射关系(鉴权概念、区域概念、通用概念)
java·开发语言
cike_y25 分钟前
JSP内置对象及作用域&双亲委派机制
java·前端·网络安全·jsp·安全开发
名誉寒冰25 分钟前
GDB 调试与 Core Dump(段错误)排查指南(Linux/C/C++)
linux·c语言·c++
也许是_27 分钟前
大模型应用技术之 Spring AI 2.0 变更说明
java·人工智能·spring
xunyan623442 分钟前
面向对象(下)-内部类的分类
java·学习
Insight.1 小时前
背包问题——01背包、完全背包、多重背包、分组背包(Python)
开发语言·python
巴拉巴拉~~1 小时前
KMP 算法通用进度条组件:KmpProgressWidget 多维度 + 匹配进度联动 + 平滑动画
java·服务器·前端