C++ - 模板分离编译

news2024/9/27 21:18:34

模板分离编译

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

// 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

#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 类来对比:

// 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()是模版参数,只有实例化之后才能生成地址。

 解决方式

 第一种是显示实例化:

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()这些函数只有声明。

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

#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. 现模板编译错误时,错误信息非常凌乱,不易定位错误

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/838282.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python 开发工具 Pycharm —— 使用技巧Lv.3

单步执行调试 1&#xff1a; 鼠标左键单击红点是断点行 2&#xff1a;甲虫样式是进行调试方式运行&#xff0c;鼠标左键单击点击 3&#xff1a; 单步运行图标&#xff0c;点击让程序运行一行 4&#xff1a; 步入步出&#xff0c;可以进入当前代码行函数内 5&#xff1a;重新运行…

JSON.stringify()与JSON.parse()没有你想的那样简单

重新学习这两个API的起因 在本周五有线上的项目&#xff0c;16:30开始验证线上环境。 开始都是顺顺利利&#xff0c;一帆风顺。 大概17:50左右&#xff0c;我正在收拾东西。 准备下班去王者峡谷骑着我的船溜达一圈。 可是天降意外&#xff0c;给我派了一个bug。 测试给我说&am…

山西电力市场日前价格预测【2023-08-06】

日前价格预测 预测明日&#xff08;2023-08-06&#xff09;山西电力市场全天平均日前电价为411.77元/MWh。其中&#xff0c;最高日前电价为457.52元/MWh&#xff0c;预计出现在19: 30。最低日前电价为370.37元/MWh&#xff0c;预计出现在13: 15。 价差方向预测 1&#xff1a; 实…

海外应用商店优化实用指南之关键词

和SEO一样&#xff0c;关键词是ASO中的一个重要因素。就像应用程序标题一样&#xff0c;在Apple App Store和Google Play中处理应用程序关键字的方式也有所不同。 关键词研究。 对于Apple&#xff0c;我们的所有关键词只能获得100个字符&#xff0c;Google Play没有特定的关键…

数据结构10 -查找_树表查找

创建二叉搜索树 二叉搜索树 二叉搜索树是有数值的了&#xff0c;二叉搜索树是一个有序树。 若它的左子树不空&#xff0c;则左子树上所有结点的值均小于它的根结点的值&#xff1b; 若它的右子树不空&#xff0c;则右子树上所有结点的值均大于它的根结点的值&#xff1b; 它…

47.Linux学习day01 基础命令详解1(很全面)

目录 一、Linux和Windows的区别 二、Linux系统目录结构 常见目录说明 三、Linux常见的基础命令 1.pwd 2.cd 3.ls 4.man 5. touch 6.mkdir 7. rmdir 今天正式学习了linux的一些基础操作和基础知识&#xff0c;以及linux和windows的区别。 一、Linux和Windows的区…

SpringMVC基于SpringBoot的最基础框架搭建——包含数据库连接

SpringMVC基于SpringBoot的最基础框架搭建——包含数据库连接 背景目标依赖配置文件如下项目结构如下相关配置如下启动代码如下Controller如下启动成功接口调用成功 背景 工作做了一段时间&#xff0c;回忆起之前有个公司有线下笔试&#xff0c;要求考生做一个什么功能&#x…

建设全球研发中心城市,长沙与人才何以双向奔赴?

跨越山涧峡谷&#xff0c;需要搭建钢铁桥梁&#xff0c;跨越文化沟通&#xff0c;需要搭建互相理解的桥梁&#xff0c;那么&#xff0c;一座城市与人才之间的联系&#xff0c;应该搭建怎样的桥梁&#xff1f; 近日&#xff0c;长沙出台了重磅文件《中共长沙市委长沙市人民政府…

XSS漏洞原理及利用跨站请求伪造CSRF

XSS漏洞原理及利用&跨站请求伪造CSRF XSS一、案例二、什么是XSS三、XSS危害四、XSS的分类4.1、反射型XSS4.1.1、介绍4.1.2、利用过程 4.2、存储型XSS4.2.1、介绍4.2.2、利用过程4.2.3、案例 4.3、DOM型XSS4.3.1、介绍4.3.2、常用的DOM方法4.3.3、案例4.3.3.1、代码分析4.3.…

Spring源码之XML文件中Bean标签的解析1

读取XML文件&#xff0c;创建对象 xml文件里包含Bean的信息&#xff0c;为了避免多次IO&#xff0c;需要一次性读取xml文件中所有bean信息&#xff0c;加入到Spring工厂。 读取配置文件 new ClassPathResource("applicationContext.xml")ClassPathResource是Sprin…

vue动态生成行

vue代码 <el-table :data"form.lineInfos" :bordertrue style"width: 99.99%;"> <el-table-column type"index" label"序号" width"50"></el-table-column> <el-table-column prop"unitPrice&qu…

【Linux 网络】 传输层协议之TCP协议 TCP的三次握手和四次挥手

TCP协议 TCP协议段格式谈谈什么是 “可靠” 和 “不可靠”TCP协议段——序号与确认序号TCP协议段——窗口大小TCP协议段 —— 六个标志位确认应答机制&#xff08;ACK&#xff09;超时重传机制连接管理机制TCP 的三次握手四次挥手TCP三次握手四次挥手总结图 滑动窗口流量控制拥…

18. SpringBoot 如何在 POM 中引入本地 JAR 包

❤️ 个人主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;成功解决 BUG 合集 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; Spring Boot 是一种基于 Spring 框架的轻量级应用程序开发框架&#xff0c;它提供了快速开发应用程…

第126天:内网安全-隧道技术SSHDNSICMPSMB上线通讯LinuxMac

知识点 #知识点&#xff1a; 1、入站规则不出网上线方案 2、出站规则不出网上线方案 3、隧道技术-SMB&ICMP&DNS&SSH 4、控制上线-Linux&Mac&IOS&Android-连接方向&#xff1a;正向&反向&#xff08;基础课程有讲过&#xff09; -内网穿透&#xf…

vue2-vue项目中你是如何解决跨域的?

1、跨域是什么&#xff1f; 跨域本质是浏览器基于同源策略的一种安全手段。 同源策略&#xff08;sameoriginpolicy&#xff09;&#xff0c;是一种约定&#xff0c;它是浏览器最核心也是最基本的安全功能。 所谓同源&#xff08;即指在同一个域&#xff09;具有以下三个相同点…

Java对象的前世今生

文章目录 一、创建对象的步骤二、类加载机制三、内存分配指针碰撞 (内存连续)空闲列表 (内存不连续) 四、创建对象的5种方法五、浅拷贝与深拷贝 以下一行代码内部发生了什么&#xff1f; Person person new Person();一、创建对象的步骤 根据JLS中的规定&#xff0c;Java对象…

牢记这16个SpringBoot 扩展接口,写出更加漂亮的代码

1、背景 Spring的核心思想就是容器&#xff0c;当容器refresh的时候&#xff0c;外部看上去风平浪静&#xff0c;其实内部则是一片惊涛骇浪&#xff0c;汪洋一片。Springboot更是封装了Spring&#xff0c;遵循约定大于配置&#xff0c;加上自动装配的机制。很多时候我们只要引…

Scala编程语言入门教程

Scala教程 方便个人学习和查阅 学习目标 Scala介绍 简介 Scala创始人Martin Odersky马丁奥德斯基 再回到我们的scala语言&#xff0c;在Scala官网https://www.scala-lang.org/介绍了其六大特征。 Java和scala可以混编 类型推测(自动推测类型) 并发和分布式&#xff08;Ac…

nginx的优化和防盗链 重要!!!

实验一、隐藏版本号 要把nginx的版本号隐藏起来&#xff0c;防止恶意攻击 方法一&#xff1a;修改配置文件 在http模块中加入一个命令 server_token off&#xff1b; 过程&#xff1a; 备份&#xff0c;改配置文件一定要备份 修改配置文件 在http模块中添加 server_tokens …

城市与AI,正待济沧海

厦门&#xff0c;自古以来是海上丝绸之路的战略支点&#xff0c;是中国海洋贸易网络中的明珠。外来的理念与技术自厦门而入&#xff0c;中国的文化与商品自厦门而出。新事物、新科技往往在这里焕发最耀眼的生机——比如说&#xff0c;人工智能。 多年来&#xff0c;厦门持续推进…