CPP-Templates-2nd--第十四章 实例化

目录

14.1 On-Demand实例化

14.2 延迟实例化

14.2.1 部分实例化和完整实例化

14.2.2 实例化组件

14.3 C++实例化模型

14.3.1 两阶段查找

14.3.2 POI 

14.3.3 包含式模型

14.4 几种实现方案

14.4.1 贪婪实例化

14.4.2 查询实例化

14.4.3 迭代实例化 


参考:cpp-templates-2nd/第14章 实例化.md at master · r00tk1ts/cpp-templates-2nd (github.com)

 

C++模板实例化的概念非常基础,但有时又错综复杂。这一复杂性的其中一个底层原因在于:模板生成的实体定义不再局限于源代码单一的位置。模板本身的位置、模板使用的位置以及模板实参定义的位置均在实体的含义中扮演着重要角色。

14.1 On-Demand实例化

on-demand:请求式、按需、点播。

当C++编译器遇到模板特化的使用时,它会用需要的实参来替换模板参数来生成特化体。这一过程是自动完成的,不需要客户端代码来引导(或者不需要模板定义来引导)。这一”on-demand“实例化特性使得C++与其他早期的编译型语言的类似功能大相径庭(如Ada或Eiffel,其中的一些语言需要显式地实例化引导,另外一些使用运行时分发机制来避免编译期实例化过程)。有时这也被称作”隐式(implicit)实例化“或者”自动(automatic)实例化“。

On-demand实例化意味着编译器常常需要访问模板完整的定义(换句话说,不只是声明)以及某些成员。考虑下面这一段精简的源码文件:

template<typename T> class C;	// #1 declaration only
C<int>* p = 0;					// #2 fine: definition of C<int> not neededtemplate<typename T>
class C{public:void f();					// #3 member declaration
};								// #4 class template definition completedvoid g(C<int>& c)				// #5 use class template declaration only
{c.f();						// #6 use class template definition;
}								// will need definition of C::f() // in this translation unittemplate<typename T>
void C<T>::f()					// required definition due to #6
{
}

另一个需要类模板实例化的表达式如下所示,这里需要C<void>实例化是因为它需要该类型的尺寸:

C<void>* p = new C<void>;

本例中,需要实例化来保证编译器可以确定C<void>的尺寸,该new表达式需要去确认要分配多少存储空间。你可能会发现,对这一模板来说,替换模板参数T的实参X的类型无论是什么,都不会影响模板的尺寸,毕竟C<X>是一个空类(没有成员变量或虚函数)。然而,编译器并不会通过分析模板定义来避免实例化(所有编译器实际上都会进行实例化)。

在源代码中是否需要访问类模板的成员并不总是那么直观。例如,C++重载决议规则要求:如果候选函数的参数是类类型,那么该类类型就必须是可见的:

template<typename T>
class C {public:C(int);		// a constructor that can be called with a single parameter
};				// may be used for implicit conversionsvoid candidate(C<double>);	// #1
void candidate(int) { }		// #2int main()
{candidate(42);		// both previous function declarations can be called
}

调用candidate(42)会采用#2处的声明。然而,在#1处的声明也会被实例化来检查对于这个调用来说它是否是可用的候选者(这个例子中,由于模板的单实参构造器可以把42隐式转换成一个类型为C<double>的右值)。请注意,如果模板不经实例化也可以找到调用函数(合适的候选),编译器还是被允许(但不强制)执行该实例化(上例的情景中,由于有精准匹配的候选者,隐式转换的那个不会被选择)。

14.2 延迟实例化

现在有一个相关问题:模板实例化的程度如何?可以给出这样的模糊答案:会实例化到它实际需要的程度。换句话说,编译器在实例化模板时应该是“懒惰”的。

14.2.1 部分实例化和完整实例化

如我们之前所见,编译器有时不需要替换类或函数模板的完整定义。例如:

template<typename T> T f(T p) { return 2*p; }
decltype(f(2)) x = 2;

本例中,decltype(f(2))所指示的类型并不需要函数模板f()的完整实例化。编译器因此只被允许替换f()的声明,而不是替换整个“身体”。这有时被称为部分实例化(partial instantiation)。

同样,如果引用类模板的实例而不需要将该实例作为完整类型,则编译器不应对该类模板实例执行完整的实例化。考虑下面的例子:

template<typename T> class Q {using Type = typename T::Type;
};Q<int>* p = 0;		// OK: the body of Q<int> is not substituted

在这里,Q<int>完整的实例化会触发一个错误,因为在Tint类型时,T::Type并没有意义。但是因为本例并不需要完整的Q<int>,所以不会执行完整实例化,代码也是OK的(尽管可疑)。

14.2.2 实例化组件

当类模板隐式(完整)实例化时,其所有成员的声明也都会进行实例化,但是对应的定义却并不会实例化(即,成员是部分实例化的)。对此有一些特殊情况:首先,如果类模板包含一个匿名的联合体(union),该联合体的成员的定义也会实例化;另一个特殊的情况出现在虚成员函数场景中,它们的定义作为模板实例化的结果,可能会也可能不会进行实例化。实际上,许多实现都会实例化该定义,因为“实现虚函数调用机制的内部结构”需要虚函数有一个链接实体存在。

14.3 C++实例化模型

模板实例化就是从对应的模板实体通过合适地模板参数替换来得到一个常规的类型、函数或是变量的过程。这可能听起来直截了当,但实际上需要遵循非常多的细节。

14.3.1 两阶段查找

在第13章中,我们曾看到依赖型名称无法在解析模板时被找到。取而代之的是,它们会在实例化的时刻再次进行查找。非依赖型名称则会在更早的阶段被查找,因此当模板第一次看到它的时候,就可以诊断出许多错误。这就引出了“两阶段查找”的概念。第一阶段查找发生在解析模板的时候,而第二阶段查找发生在模板实例化的时候

  1. 在第一阶段,当解析模板时,非依赖型名称会并用一般查找规则和ADL规则(如果可行的话)。非限定依赖型名称(诸如函数调用中的函数名称,它们之所以是依赖型名称,是因为它们具有依赖型实参)会使用普通查找规则,但是这一查找结果并不会作为最终结果,而是要等到第二阶段的另一个查找过程完成(也就是模板实例化的时候)。
  2. 在第二阶段,此时的模板实例化被称作POI(point of instantiation),依赖型限定名称会在此时被查找(对选定的实例用模板实参替换模板参数),而且还会对非限定依赖型名称进行额外的ADL查找(它们曾在第一阶段进行过普通查找)。

14.3.2 POI 

如上所述,C++编译器会在模板客户端代码的某些位置访问模板实体的声明或者定义。当某些代码结构引用了模板特化,而且为了生成该特化需要实例化相应的模板定义时,就会在源代码中产生一个POI。POI是源代码中的一个点,在这里会插入已被替换的模板。例如:

class MyInt {public:MyInt(int i);
};MyInt operator - (MyInt const&);bool operator > (MyInt const&, MyInt const&);using Int = MyInt;template<typename T>
void f(T i)
{if(i > 0) {g(-i);}
}// #1 
void g(Int)
{// #2f<Int>(42);	// point of call// #3
}
// #4

C++编译器看到f<Int>(42)时,它知道模板f需要用MyInt替换T来实例化:这就产生了一个POI。#2#3与该调用点紧邻,但是它们都不适合做POI,因为C++不允许我们在这里插入::f<Int>(Int)的定义。此外,#1#4两处的本质区别在于,在#4处,函数g(Int)是可见的,因此模板依赖的调用g(-i)可以在#4处被解析。然而,如果我们假定#1是POI的话,那么调用g(-i)将不能被解析,因为g(Int)#1处是不可见的。幸运的是,对于函数模板特化的引用,C++把它的POI定义,置于紧跟在“包含这个引用的定义或声明所在的最近的命名空间作用域”之后。在我们的例子中,这个位置就是#4

变量模板POI的处理与函数模板相似。而对于类模板特化来说,情况则不太一样,如下例所示:

template<typename T>
class S {public:T m;
};// #1 
unsigned long h()
{// #2return (unsigned long)sizeof(S<int>);// #3
}
// #4

老规矩,#2#3都不能作为POI,这两个位置不能进行命名空间作用域类S<int>的定义(模板是不能出现在函数作用域内部的)。假如我们可以遵循函数模板实例的规则,POI将会出现在位置#4处,然而,这样一来,表达式sizeof(S<int>)是无效的,这是因为S<int>的尺寸直到#4之后才能被确定。因此,生成的类模板实例的引用被紧邻地定义在包含该引用的声明或定义的命名空间作用域之前。在我们的例子中,这个位置就是#1

14.3.3 包含式模型

当遇到POI时,对应模板的定义必须是可访问的。对类特化来说,这意味着类模板定义必须在编译单元中被更早地看见。而对函数模板和变量模板(以及类模板的成员函数和静态数据成员)的POI来说,也同样需要。典型的模板定义被简单的通过#include语句引入到编译单元,尽管是非类型模板也一样。这种模板定义的源码模型被称为包含式模型,它目前是当下C++标准所支持的模板的唯一自动源码模型。

14.4 几种实现方案

14.4.1 贪婪实例化

贪婪实例化假定链接器会意识到特定的实体(尤其是可链接的模板实例化体),它们大多在多个目标文件和库中重复出现。编译器会以一种特殊的方式标记这些实体。当链接器发现了多个实例时,它会保留单个并丢弃掉所有其他的。这就是贪婪实例化的处理方法。

14.4.2 查询实例化

在这一方案中,程序中参与的所有编译单元会汇集一个共享的数据库。该数据库可以追溯哪些特化体被实例化了,并且可以找到其所依赖的源代码。生成的特化体本身会把信息存储在数据库中。当可链接实体遇到一个POI时,会进入下面的处理流程:

  1. 尚无可用的特化体:这种情况会进行实例化,特化的结果会保存到数据库中。
  2. 特化体虽可用但超期了,因为自它生成以来源代码发生了变化。这种情况同样会进行实例化,新的特化结果会覆盖数据库中旧的那一个。
  3. 数据库中有最新可用的特化体。这种情况什么都不用做。

14.4.3 迭代实例化 

Cfront的迭代过程如下所述:

  1. 编译源代码,此时不要实例化任何需要链接的特化体
  2. 使用预链接器(prelinker)链接目标文件
  3. 预链接器调用链接器,解析错误信息,判断是否缺少某个实例化体。如果缺少的话,预链接器会调用编译器,来编译包含所需模板定义的源代码,然后(可选地)生成该缺少的实例化体。
  4. 重复第3步,直到不再生成新的定义。

第3步中,这种迭代的要求基于这样的事实:在实例化一个可链接实体过程中,可能会要求”另一个仍未实例化“的实体进行实例化;最后,所有的迭代都已经完成,链接器才会成功创建一个完整的程序。

 

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

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

相关文章

jvm 内存模型介绍

一、类加载子系统 1、类加载的过程&#xff1a;装载、链接、初始化&#xff0c;其中&#xff0c;链接又分为验证、准备和解析 装载&#xff1a;加载class文件 验证&#xff1a;确保字节流中包含信息符合当前虚拟机要求 准备&#xff1a;分配内存&#xff0c;设置初始值 解析&a…

数据分析工具有哪些,哪个好学?

Tableau、帆软BI、思迈特BI、SpeedBI数据分析云……这些都是比较常见的BI数据分析工具。从学习成本、操作难度以及数据可视化分析效果来看&#xff0c;SpeedBI数据分析云都表现地可圈可点。 1、不需下载安装、学习成本低 SpeedBI数据分析云是一款SaaS BI数据分析工具&#xf…

【AIGC专题】Stable Diffusion 从入门到企业级实战0601

一、前言 本章是《Stable Diffusion 从入门到企业级实战》系列的第六部分Prompt专题篇《Stable Diffusion Prompt 专题》第01节 《Stable Diffusion Prompt 通用画风操作实战》。本部分内容&#xff0c;位于整个Stable Diffusion生态体系的位置如下图黄色部分所示&#xff1a;…

【简单教程】利用Net2FTP构建免费个人网盘,实现便捷的文件管理

文章目录 1.前言2. Net2FTP网站搭建2.1. Net2FTP下载和安装2.2. Net2FTP网页测试 3. cpolar内网穿透3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 文件传输可以说是互联网最主要的应用之一&#xff0c;特别是智能设备的大面积使用&#xff0c;无论是个人…

Python中异常处理4-4

在Python中的异常处理4-1_棉猴的博客-CSDN博客中提到&#xff0c;在try块中的代码运行时如果出现异常&#xff0c;会自动抛出这个异常。可以通过raise语句手动抛出异常。 1 raise语句手动抛出异常 raise后面跟要抛出的异常类或者异常类的实例&#xff0c;表示手动抛出该异常&…

翻牌闯关游戏

翻牌闯关游戏 3关&#xff1a;关卡由少至多12格、20格、30格图案&#xff1a;12个玩法&#xff1a;点击两张卡牌&#xff0c;图案一到即可消除掉 记忆时长(毫秒)&#xff1a;memoryDurationTime:5000 可配置&#xff0c;默认5000 提示游戏玩法&#xff1a;showTipsFlag:1 可…

【分布式】分布式ID

目录 前言一、雪花算法snowflake1. 组成2. 优缺点3. 时钟回拨怎么解决a. 时钟回拨b. 解决方案 4. 项目中如何使用 二、基于Redis三、基于Zookeeper四、号段模式五、指定步长的自增ID六、UUID参考 六、扩展总结 前言 分布式场景下&#xff0c;一张表可能分散到多个数据结点上。因…

uniapp——实现在线选座功能——技能提升

首先声明一点&#xff1a;下面的内容是从一个uniapp的程序中摘录的&#xff0c;并非本人所写&#xff0c;先做记录&#xff0c;以免后续遇到相似需求抓耳挠腮。 这里写目录标题 效果图代码——html部分cu-custom组件anil-seat组件 代码——jscss部分 效果图 代码——html部分 …

gin-基础笔记

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、get和post方法二、重定向总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;随着人工智能的不断发展&#xff0…

uniapp掉完接口后刷新当前页面方法

uniapp掉完接口后刷新当前页面方法 掉完接口&#xff0c;里面加下面这个方法uni.redirectTo({}) setTimeout(() > {uni.redirectTo({// 当前页面路由url: /pages/property/mutualrotation/mutualrotation);}, 500)实例 mutualRotationSubmit() {let self this;uni.showMod…

Python Opencv实践 - 视频文件操作

参考资料&#xff1a; 视频处理VideoCapture类---OpenCV-Python开发指南&#xff08;38&#xff09;_python opencv videocapture_李元静的博客-CSDN博客 OpenCV VideoCapture.get()参数详解 - 简书FOURCC四字符码对照表_4fvcc_Kellybook的博客-CSDN博客 import cv2 as cv im…

网络基础--1.网络纵横

网络的发展历程 计算机由原来的只能单一处理信息&#xff08;单用户批处理&#xff09;逐步发展为多用户批处理&#xff0c;可以实现一台计算机连接多个终端同时使用一台计算机&#xff08;分时系统&#xff09;&#xff0c;但是多个终端之间不能相互通信&#xff0c;再发展成为…

API接口文档管理系统平台搭建(更新,附系统源码及教程)

简介 这是一款简洁大方的API接口文档管理系统&#xff0c;附系统源码及教程方法。可以轻松管理和使用API接口。 安装步骤 打开config/database.php配置数据库信息导入数据库data.sql设置运行目录为/public伪静态设置think PHP后台地址/admin/login.html 账号&#xff1a;adm…

git 远程名称 远程分支 介绍

原文&#xff1a; 开发者社区> 越前君> 细读 Git | 让你弄懂 origin、HEAD、FETCH_HEAD 相关内容 读书笔记&#xff1a;担心大佬文章搬家&#xff0c;故整理此学习笔记 远程名称&#xff08;Remote Name&#xff09; Origin 1、 origin 只是远程仓库的一个名称&#xff…

【Kafka】Kafka再平衡机制及相关参数

背景 Kafka作为一款基于发布订阅模式的消息队列&#xff0c;生产者将消息发送到Kafka集群&#xff08;Brokers&#xff09;中&#xff0c;消费者&#xff08;Consumer Group &#xff09;拉取消息进行消费&#xff0c;实现了异步机制。Kafka中&#xff0c;消费者通常以消费者组…

解决方案| anyRTC远程检修应用场景

背景 在这个科技飞速发展的时代&#xff0c;各行各业都要求高效运转。然而&#xff0c;当出现问题时&#xff0c;我们却常常因为无法及时解决而感到困扰&#xff0c;传统解决问题的方式是邀请技术人员现场解决问题&#xff0c;如果技术人员解决不了&#xff0c;还要邀请专家从…

编程参考 - std::exchange和std::swap的区别

这两个功能是C standard library中的Standard template library中的一部分。容易混淆&#xff0c;我们来看下它们的区别。 exchange&#xff1a; 这个函数是一个返回原先值的set函数。 std::exchange is a setter returning the old value. int z std::exchange(x, y); Af…

C语言指针详解(3)———指针题目,你确定你学会指针了?进来看看吧!(几十个指针小题+超详解)

你确定你学会指针了&#xff1f; 你确定你明白数组名了&#xff1f; 如果你觉得你学的还不错&#xff0c;就进来看看吧&#xff0c;相信你看完之后一定能收获更多。 数组名的理解一定要弄清楚 数组名是数组首元素的地址 但是有2个例外&#xff1a; sizeof(数组名)&#xff0c;这…

【MySQL多表查询以及事务、索引】

1. 多表查询 1.1 概述 1.1.1 数据准备 #建议&#xff1a;创建新的数据库 create database db04; use db04;-- 部门表 create table tb_dept (id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称…

Jmeter 实现 mqtt 协议压力测试

1. 下载jmeter&#xff0c;解压 https://jmeter.apache.org/download_jmeter.cgi 以 5.4.3 为例&#xff0c;下载地址&#xff1a; https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.4.3.zip linux下解压&#xff1a; unzip apache-jmeter-5.4.3.zip 2. 下载m…