C++ - 模板分离编译
模板分离编译
我们先来看一个问题,我们用 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.hnamespace My_stack
{class A{public:void fun1();void fun2();};
}// stack.cppnamespace 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();}templateclass 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 也是可以的。
模版的优缺点
【优点】
- 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
- 增强了代码的灵活性
【缺陷】
- 模板会导致代码膨胀问题,也会导致编译时间变长
- 现模板编译错误时,错误信息非常凌乱,不易定位错误
相关文章:
C++ - 模板分离编译
模板分离编译 我们先来看一个问题,我们用 stack 容器的声明定义分离的例子来引出这个问题: // stack.h // stack.h #pragma once #include<deque>namespace My_stack {template<class T, class Container std::deque<T>>class stack…...
如何把非1024的采样数放入aac编码器
一. aac对数据规格要求 二、代码实现 1.初始化 2.填入数据 3.取数据 三.图解 一. aac对放入的采样数要求 我们知道aac每次接受的字节数是固定的,在之前的文章里有介绍libfdk_aac音频采样数和编码字节数注意 它支持的采样数和编码字节数分别是: fdk_aac …...
linux安装nodejs和vue
下载nodejs 打开 下载地址页面中下载**Linux Binaries (x64)**的二进制包设置安装目录 sudo mkdir -p /usr/local/lib/nodejs # 解压 如下载的 node-v18.17.0-linux-x64.tar.xz sudo tar -xJvf node-v18.17.0-linux-x64.tar.xz -C /usr/local/lib/nodejs 加入到PATH #######…...
spring整合mybatis
所需配置: <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><dependency><groupId>m…...
Spring指定bean在哪个应用加载
1.背景 某项目,spring架构,有2个不同的WebAppApplication入口,大部分service类共用,小部分类有区别,只需要在一个应用中加载,不需要在另一个应用中加载. 2.实现代码 自定义限制注解 package mis.shared.annotation;import java.lang.annotation.ElementType; import java.lan…...
二维网格划分 LRU缓存设计
背景 有大量的二维矩形需要存储查看点在哪些矩形中给定一个矩形 查看与哪些矩阵相交项目背景与图形图像基本无关,只涉及大文件分块读取,所以不用实现游戏行业中的物理引擎 设计思路 使用空间划分算法:二维栅格将整个空间划分为多个小区域。…...
C++中使用 sizeof 确定变量的长度
C中使用 sizeof 确定变量的长度 变量长度指的是程序员声明变量时,编译器将预留多少内存,用于存储赋给该变量的数据。变量的长度随类型而异, C 提供了一个方便的运算符——sizeof,可用于确定变量的长度(单位为字节&…...
我们的衣物收纳商品政策
本政策涵盖的衣物收纳商品 衣物收纳商品是指带有抽屉或铰链门的家具商品,用于存放衣物。此政策适用于独立式衣物收纳商品,包括但不限于高度为 27 英寸(69 厘米或 686 毫米)或更高(从地面到商品顶部测量)的…...
代码随想录算法训练营第25天| 第七章 回溯算法part02: leetcode 216、leetcode 17
Part I : 回溯算法基础 对回溯算法不清楚的可以参看前一篇:代码随想录算法训练营第24天| 第七章 回溯算法part01 理论基础、leetcode 77 Part II: 相关题目 Leetcode 216.组合总和III 解决问题:在数字1~9之间,找出k个数且它们的和为n从而…...
WebAPI文档与自动化测试
目录 1、控制器,项目属性里需要勾选输出Xml文档选项: 2、下载文档的网页数据 3、运行访问网址 4、接口测试: 5、批量测试: 6、微服务文档 总结: 本篇介绍框架的WebAPI文档与自动化测试 1、控制器,项…...
netty架构
https://zhuanlan.zhihu.com/p/181239748 https://cloud.tencent.com/developer/article/1754078...
拉普拉斯平滑算法
原理 最简单的拉普拉斯平滑算法的原理是将每个顶点都移动到相邻顶点的平均位置上。公式 示例(UE5代码片段) 参考 https://blog.csdn.net/mrbaolong/article/details/105859109...
Java课题笔记~ IoC 控制反转
二、IoC 控制反转 控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的 装配和管理。控制反转就是对对象控制权的转移&a…...
【Spring】Spring中的设计模式
文章目录 责任链模式工厂模式适配器模式代理模式模版方法观察者模式构造器模式 责任链模式 Spring中的Aop的通知调用会使用责任链模式责任链模式介绍 角色:抽象处理者(Handler)具体处理者(ConcreteHandler1)客户类角…...
【ChatGLM_02】LangChain知识库+Lora微调chatglm2-6b模型+提示词Prompt的使用原则
经验沉淀 1 知识库1.1 Langchain知识库的主要功能(1) 配置知识库(2) 文档数据测试(3) 知识库测试模式(4) 模型配置 2 微调2.1 微调模型的概念2.2 微调模型的方法和步骤(1) 基于ptuning v2 的微调(2) 基于lora的微调 3 提示词3.1 Prompts的定义及原则(1) Prompts是什么…...
构建未来移动应用:探索安卓、iOS和HarmonyOS的技术之旅
安卓、iOS和HarmonyOS的比较分析 在移动应用开发领域,安卓、iOS和HarmonyOS是三个常见的操作系统。本文将对它们进行比较分析,并展示一些相关的代码示例。 安卓(Android) 安卓是由Google开发的移动操作系统,基于Lin…...
【新版系统架构补充】-嵌入式软件
嵌入式软件 嵌入式软件是指应用在嵌入式计算机系统当中的各种软件,除了具有通用软件的一般特性,还具有一些与嵌入式系统相关的特点,包括:规模较小、开发难度大、实时性和可靠性要求高、要求固化存储。 嵌入式软件分类࿱…...
【云原生】K8S超详细概述
目录 一、Kubernets概述1.1 K8S什么1.2为什么要用K8S 二、Kubernetes 集群架构与组件2.1Master组件Kube-apiserverKube-controller-managerKube-scheduler 2.2 配置存储中心etcd 2.3 Node 组件KubeletKube-Proxydocker 或 rocket 三、 Kubernetes 核心概念3.1Pod3.2Pod 控制器K…...
(五)Node.js -模块的加载机制
1. 优先从缓存中加载 模块在第一次加载后会被缓存。这意味着多次调用require()不会导致模块的代码被执行多次。 注意:不论是内置模块、用户自定义模块、还是第三方模块,它们都会优先从缓存中加载,从而提高模块的加载效率。 2. 内置模块的加载…...
【docker】Windows11系统下安装并配置阿里云镜像加速
【docker】Windows11系统下安装并配置阿里云镜像加速 提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 文章目录 【docker】Windows11系统下安装并配置阿里云镜像加速一、查看Windows环境是否支持docker二、 启动Hyper-V三、 官网下载安装Docker应用和数据…...
无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
RabbitMQ入门4.1.0版本(基于java、SpringBoot操作)
RabbitMQ 一、RabbitMQ概述 RabbitMQ RabbitMQ最初由LShift和CohesiveFT于2007年开发,后来由Pivotal Software Inc.(现为VMware子公司)接管。RabbitMQ 是一个开源的消息代理和队列服务器,用 Erlang 语言编写。广泛应用于各种分布…...
