模板高级使用(非类型模板参数,特化,分离编译)

news2024/11/15 21:46:43

在这里插入图片描述

文章目录

    • 模板没有实例化取内嵌类型报错问题
    • 非类型模板参数
    • 模板的特化
      • 函数模板的特化
      • 类模板的特化
        • 1.全特化
        • 2.偏特化
    • 模板的分离编译

模板没有实例化取内嵌类型报错问题

首先在这里分享一个模板的常见报错问题。就是模板的在没有实例化的情况下去取模板类里面的内嵌类型这时候的报错问题。

报错信息:
error C2760: 语法错误: 意外标记 “标识符”,应为 “;”
error C7510: “const_iterator”: 类型 从属名称的使用必须以“typename”为前缀
image.png

错误就在框线里面的哪一行代码,这段代码是使用模板编写的可以打印任意容器内的数据的函数。
报错的原因是在编译的时候,Con作为一个模板参数这时候还没有实例化出具体类型,编译器并不认识Con以及后面这一块是什么东西,因为类型没有实例化也就不能去取Con的内嵌类型。所以这时候就报错了。加上typename就是告诉编译器,Con是一个模板类型,等Con实例化之后再去取Con的内嵌类型。相当于是一个声明。

这里可以类比函数声明,如果调用了一个函数没有声明也没有定义,那么这时候是编译不通过的,报错就是找不到”xxx“标识符,这个错误是在编译的时候进行语法检查的时候检查出来的。如果加上这个函数的声明,那么编译时不会报错的。因为有了声明就告诉了编译器这个函数存在,你等着链接的时候再去找,如果在链接的时候还是找不到就会报错链接错误。
这里以add函数为例:image.png
现在加上函数声明,但是不写函数定义。
image.png
这时候报的就是LNK也即是链接错误。

非类型模板参数

以前使用的模板都是将类型定义成模板参数,比如内置类型int等以及自定义类型等等。但是其实常量也是可以作为模板参数的。

#include<iostream>

using namespace std;

template<class T,size_t N>
class Time
{
public:

private:
	T a[N];
};


int main()
{
	Time<int,100> t1;

	return 0;
}

如上这里的第二个模板参数就是一个常量,但是非类型模板参数只能是int类型的常量,其他比如:double,字符串或者自定义类型变量那么都是不行的。
在类内部这个非类型模板参数也是当作常量使用的,因为定义数组的大小只能使用常量(不考虑C99中的变长数组)
其次模板必须在编译的时候就能确定模板参数的类型,这也决定了模板不能够分离编译,一旦分离就会报错。

模板的特化

使用模板可以让编译器替我们完成一些重复的工作,同时也可忽略类型,让我们只关注代码逻辑的本身,但是有些特殊情况模板的默认处理逻辑并不能完成我们的需要,这时候就有了模板的特化。

函数模板的特化

比如模板定义了一个比较函数

template<class T> 
void compare(const T& x, const T& y)
{
	return x > y;
}

可以给这个函数传int,double等等,编译器都会自动生成一份与其匹配的代码,但是当想要比较字符串的时候。比如下面:

template<class T> 
bool compare(const T& x, const T& y)
{
	return x > y;
}


int main()
{
	//Time<int,100> t1;
	char arr1[] = "hello kisskernel";
	char arr2[] = "hello kisskernel";
	cout << compare(arr1, arr2) << endl;

	const char* p1 = "hello kisskernel";
	const char* p2 = "hello kisskernel";
	cout << compare(p1, p2) << endl;

	return 0;
}

这时使用模板生成的比较函数,比较的是地址,而不是字符串,所以就需要模板的特化来解决这种特殊情况。
这里有两种解决方案:
1.直接写一个重载函数,对于特殊情况完成特殊处理(推荐使用)。
2.将函数模板根据特殊情况进行特化,也叫做专用化,实际也是自己写一个函数。
并且比写一个重载函数还要复杂,所以一般推荐使用第一种方式解决。

1.重载

bool compare(char* x,char* y)
{
	return strcmp(x, y) == 0;
}
bool compare(const char* x, const char* y)
{
	return strcmp(x, y) == 0;
}

针对上面的arr1/arr2类型char重载一个函数,针对const char又重载了一个函数。

2.模板特化

//针对char*类型的参数
template<>
int compare<char*>(char* const& left, char* const& right)
{
	cout << "compare<char*>" << endl;
	return strcmp(left, right);
}
//针对const char* 的两种特化方式
template<>
int compare<const char*>(const char* const& left, const char* const& right)
{
	cout << "compare<const char*>" << endl;
	return strcmp(left, right);
}
//template < >
//int compare(const char* const& left, const char* const& right)
//{
//	cout << "in special template< >..." << std::endl;
//
//	return strcmp(left, right);
//}

因为C++的函数模板的特化规则过于诡异,很容易就会出现奇怪的报错。
比如下面这句报错:
error C2912: 显式专用化;“int compare<const char*>(const char ,const char )”不是函数模板的专用化
这种错误,不同的时将括号内的参数类型换一下,这些都是因为特化的函数模板没有和基础模板的参数完全相同。
所以这里详细讲解一下特化方式。
函数模板步骤
1.首先必须要有一个基础模板,也即是通用的模板
2.特化的时候template后面必须有尖括号<>
3. 函数形参表: 必须要和模板函数的基础参数类型完全相同
**注意:如果基础的模板参数带有const,比如上面代码通用模板的类型时const T&,我们特化出来的类型目的是将T替换,但是这时候特化后的T应该写在cosnt &的前面不然就会报错。因为如果T指针类型如int,那么const修饰的意义就变了,如果不改变T位置就是:const int& left,const从修饰&变成了修饰指针。之前是a指向的对象不能改变,现在变成int
不能改变了,所以要将T改变到const之前,不改变const的修饰意义。int
const &

如果基础模板的参数没有const那就无所谓了,但是如果参数时引用,那么特化后的参数也必须时引用。
下面代码简单演示:

template <class T>
int compare(T& left, T& right)
{
	std::cout << "in template<class T>..." << std::endl;
	return strcmp(left, right);
}

template<>
int compare<char*>(char*& left, char*& right)
{
	return 0;
}
template<>
int compare<const char*>(const char*& left, const char*& right)
{
	return 0;
}

类模板的特化

类模板的特化分为全特化和偏特化。

1.全特化

顾名思义就是将模板参数全部确定化。


template<class T1,class T2>
class Date
{
public:
	Date()
	{
		cout << "template<class T1,class T2>" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

template<>
class Date<int,char>
{
public:
	Date()
	{
		cout << "Date<int,char>" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

模板全特化这里就时根据特殊情况来实例化一个类完成特殊化处理。

2.偏特化

偏特化有的书上也叫做半特化,但是很少这样叫。
偏特化就是针对模板参数进行一些限制处理。

template<class T1, class T2,class T3>
class Date
{
public:
	Date()
	{
		cout << "template<class T1,class T2>" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
template<class T1,class T2>
class Date<T1, T2, char>
{
public:
	Date()
	{
		cout << "Date<int,char>" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

template<class T1>
class Date<T1, char,int>
{
public:
	Date()
	{
		cout << "Date<int,char>" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

比如这里三个模板参数,可以设置一个参数特化,或者时两个参数特化,但是必须是从右向左,且必须是连续的。这点类似于缺省参数。

偏特化除了限定参数为int,double等等之外还可以限定参数的类型,比如限定为T*或者是T&等。

template<class T1, class T2,class T3>
class Date
{
public:
	Date()
	{
		cout << "template<class T1,class T2>" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

template<class T1, class T2, class T3>
class Date<T1*,T2*,T3&>
{
public:
	Date()
	{
		cout << "template<class T1,class T2>" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

比如这种就是模板限定了参数的类型。进行了更进一步的限制。

模板的分离编译

模板之前就说过是不支持分离编译的,会报出链接错误,分离编译的意思就是(声明在.h文件,定义的.cpp文件中)首先先讲为什么要进行分离编译呢?分离编译的优点是:便于维护并且可以增强工程代码的可读性。因为函数的声明,和类的框架都在头文件中。如果只需要了解一下大体框架只需要看头文件就可以了解了。细节内容就可以去cpp文件中查看。

模板为什么不支持分离编译

首先需要了解编译链接的过程,程序要经过预处理,编译,汇编,链接四个过程最后形成可执行文件。模板需要在编译之前推导出T的具体类型,然后实例化出具体的代码,然后再将实例化的代码编译为指令。

但是模板分离编译的时候cpp文件时分开编译的,在最后链接之前互相是独立的。所有此时模板不能推导出T的具体类型就不会实例化出具体代码。但是因为.h文件中包含函数的声明,所以main.cpp调用的地方可以走到链接,在链接的时候去找具体实现,此时因为找不到就会爆出链接错误。因此模板不支持分离编译。

解决方法:

  1. 将定义和实现都放在.h文件中。
  2. 模板显式实例化,在实现定义的cpp文件中显示指定T的类型。
template
class Test<int>

template
void Func<int>(int a, int b)

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

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

相关文章

【面试题】HashMap为什么可以插入null而Hashtable就不可以(源码分析)

首先hashmap可以插入null值&#xff0c;但是hashtable和hashcurrentHashmap是不支持的&#xff1b;这是因为在 hashmap对插入key为null进行了特殊处理&#xff0c;当插入的值为null的时候会将哈希值设置为0 但是hashtable会直接抛出异常&#xff1a; 并且hashmap是线程不…

2024全国水科技大会【发言单位】天健水务集团(杭州)有限公司

天健水务&#xff0c;始创于2003年&#xff0c;下属浙江天行健水务有限公司、杭州天勤水处理技术有限公司、杭州天行健新能源有限公司&#xff0c;是一家致力于现代化水处理设备与系统研发、生产及工程应用的国家高新技术企业。以天健智造、天健工程、天健运维的“一站式全流程…

uniapp ios证书失效

前面是按照网上查找的方法 作者大大的地址 1、一个ios账户&#xff08;688付费版&#xff09; 2、登录 Apple Developer 3、创建Identifiers ps&#xff1a;创建时需继承苹果的sdk&#xff0c;只需要一个就行 点击continue再点击Register即可 4、创建.cer证书 &…

软件测试简历,你真的会写简历吗?一周疯狂面试6家...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 简历元素 一份合格…

SqlServer数据库复习总结资料

基于课堂上学到的以及书上的看到的&#xff0c;总结出的数据库复习资料 一、数据库概述 基本概念 1.数据 数据&#xff08;Data&#xff09;是事物的符号表示&#xff0c;可以是声音、图像、文字、数字&#xff0c;也可以是计算机代码。 2.数据库 数据库&#xff08;DataBase…

WebSocket 使用示例,后台为nodejs

效果图 页面代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>WebSocket Client</title&g…

java NIO群聊系统

demo要求&#xff1a; 1&#xff09;编写一个NIO群聊系统&#xff0c;实现服务器端和客户端之间的数据简单通讯&#xff08;非阻塞&#xff09; 2&#xff09;实现多人群聊 3&#xff09;服务器端&#xff1a;可以监测用户上线&#xff0c;离线&#xff0c;并实现消息转发功…

利用autodl服务器跑模型

1. 租用服务器 本地改模型 服务器 将改进好的、数据集处理好的模型压缩为zip文件上传到阿里云盘打开服务器AUTODL服务器&#xff0c;在主页中选择容器实例 在此位置进行开关机操作&#xff0c;若停止服务器&#xff0c;必须关机&#xff0c;不然会一直扣钱 2. 运行模型 选择…

【Unity】捕捉PC桌面的插件

【背景】 之前介绍了如何用一款名为uWindowCapture的Unity免费插件在Unity的Canvas上展示PC桌面。经过一段时间的使用,本篇继续分享此插件的一些功能和限制。 在此感谢作者Hecomi。 【特征和限制】 一般局域网络环境只能最多达到15帧的帧率,所以别幻想用来窜流游戏或者看电…

【个人记录】CentOS7安装MySQL 5.7和libmysqlclient.so.20

记录 之前使用MariaDB 发现使用的libmysqlclient.so是18版本的&#xff0c;一些程序需要20版本的库&#xff0c;查了一下需要安装5.7以上版本的才有libmysqlclient.so.20&#xff0c;这里简单记录一下怎么安装。 安装MySQL 5.7 Yum源 yum install -y https://repo.mysql.com…

【C++】狗屁不通文章生成器2.0

【C】狗屁不通文章生成器2.0 1 前言2 改进2.1 字词的前后关系2.2 文章生成系统 3 实现(部分)3.1 class wordpair3.1.1 转化为 json3.1.2 添加后缀词3.1.3 选择后缀词 3.2 class createArticle3.2.1文本分割3.2.2生成文章 4演示4.1 wordpair(3x2), 启动词(春天)4.2 wordpair(2x1…

电脑维修的相关资料,有需要的自取

电脑维修的相关资料&#xff0c;有需要的自取。 链接&#xff1a;https://pan.baidu.com/s/1X81sBNAOmomFvug6mK56Bw 提取码&#xff1a;52pj 爆笑幽默段子&#xff1a;电脑出故障了&#xff0c;准备拿去修&#xff0c;结果被女朋 友拦住了。女朋友&#xff1a;“你们男人一定…

ginblog博客系统/golang+vue

ginblog博客系统 前台&#xff1a; 后台&#xff1a; Gitee的项目地址&#xff0c;点击进入下载 注意&#xff1a; 数据库文件导入在model里面&#xff0c;直接导入即可。 admin和front前后台系统记住修改https里的地址为自己的IP地址&#xff1a; front同上。

Doris实战——工商信息查询平台的湖仓一体建设

目录 前言 一、架构1.0&#xff1a;传统Lambda架构 二、OLAP引擎调研 三、架构2.0&#xff1a;数据服务层All in Apache Doris 四、架构 3.0&#xff1a;基于Doris Multi-Catalog的湖仓一体架构 五、实践经验 5.1 引入Merge-on-Write&#xff0c;百亿级单表查询提速近三…

学习vue3第九节(新加指令 v-pre/v-once/v-memo/v-cloak )

1、v-pre 作用&#xff1a;防止编译器解析某个特定的元素及其内容&#xff0c;即v-pre 会跳过当前元素以及其子元素的vue语法解析&#xff0c;并将其保持原样输出&#xff1b; 用于&#xff1a;vue 中一些没有指令和插值表达式的节点的元素&#xff0c;使用 v-pre 可以提高 Vu…

【项目实践】VS配置Qt

文章目录 前言版本使用具体步骤1&#xff09;安装Qt或者添加删除组件2&#xff09;VS安装Qt Visual Studio Tools 如何使用遇到的问题双击ui文件编辑报错 前言 最近因为一个项目&#xff0c;需要使用Qt&#xff0c;本来想使用Python的&#xff0c;但是由于另外一个第三方的库是…

反诈提醒:谨防私人财务、跑分类项目

文章目录 引言I 私人财务的特征II “跑分”的本质III 妥善做好个人账户管理IV 处理非柜面交易限制V 个人账户收款监管规则VI 警惕“手机口”诈骗VII 反诈提醒引言 一切需要你的账户入资和出资的,进行资金中转的都是洗钱。 发现自己身边有人涉嫌买卖个人信息、手机卡、银行卡…

Java设计模式之单例模式(多种实现方式)

虽然写了很多年代码&#xff0c;但是说真的对设计模式不是很熟练&#xff0c;虽然平时也会用到一些&#xff0c;但是都没有深入研究过&#xff0c;所以趁现在有空练下手 这章主要讲单例模式&#xff0c;也是最简单的一种模式&#xff0c;但是因为spring中bean的广泛应用&#…

JVM垃圾回收之内存分配,死亡对象判断方法

Java 堆是垃圾收集器管理的主要区域&#xff0c;因此也被称作 GC 堆。 堆划分为新生代 老生代 永久代。 下图所示的 Eden 区、两个 Survivor 区 S0 和 S1 都属于新生代&#xff0c;中间一层属于老年代&#xff0c;最下面一层属于永久代。 内存分配原则 对象优先在Eden区域分…

算法打卡day11

今日任务&#xff1a; 1&#xff09;239. 滑动窗口最大值 2&#xff09;347.前 K 个高频元素 239. 滑动窗口最大值 题目链接&#xff1a;239. 滑动窗口最大值 - 力扣&#xff08;LeetCode&#xff09; 给定一个数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移…