[C++] 模板进阶:特化与编译链接全解析

news2024/11/27 7:40:39

Kevin的技术博客.png

文章目录

  • 非类型模板
    • 类型形参
    • 非类型模板参数
    • 代码示例
  • **模板的特化**
    • 为什么要有模板的特化
    • 函数模板特化
      • 使用场景与示例
      • 函数模板特化的实现细节
    • 类模板特化
      • 全特化
          • 示例
      • 偏特化
        • 部分优化
        • 通过进一步限制模板参数进行特化
          • 偏特化为指针类型示例:
          • 偏特化为引用类型示例:
    • 特化测试结果分析
  • 模板特化中的注意事项
    • 实例化时严格的匹配性
    • 指针特化时`const`的修饰问题
      • 为什么在参数列表使用`const`?
      • `const`与指针修饰关系
        • **指针本身是常量** (`const`在`*`之后)
        • **指向的内容是常量** (`const`在`*`前面)
      • 指针特化时`const`修饰的应用
    • 已经特化的类中`T`表示为什么?
  • 模板的分离编译
    • 分离编译模式简介
    • 模板的分离编译
      • 分离编译测试
      • 原因解析
        • C/C++程序的编译链接原理
        • 为什么不能分离定义?


非类型模板

模板参数分为:类型形参和非类型形参

类型形参

类型形参,即在模板初阶中所用的例如class Atypename A此类参数类型,跟在classtypename后。
[C++] 模版初阶-CSDN博客

非类型模板参数

非类型模板参数,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用,定义方法如下:

template<class T, size_t N = 10>

注意:

  1. 非类型模板参数只能是整型。
  2. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  3. 非类型的模板参数必须在编译期就能确认结果(原因看下文)。

代码示例

template<int N>
class Array {
public:
    int arr[N];
    // 其他方法
};
Array<5> myArray; // 创建一个包含5个元素的数组对象

模板的特化

为什么要有模板的特化

模板技术提供了强大的泛型编程能力,使得我们能够编写与数据类型无关的代码,从而提高代码的复用性和灵活性。然而,在实际应用中,有时需要对特定类型进行特殊处理,这时就需要用到模板特化

**注意:**一般情况下如果函数模板遇到不能处理或者处理有误的类型,为了实现简单通常都是将该函数直接给出

模板特化的出现是为了解决模板在处理某些特殊类型时可能遇到的问题。例如,一个通用的比较函数模板可以比较大多数类型的数据,但在遇到指针时,仅比较指针的地址而不是指向的内容,这就可能导致错误的结果。模板特化允许为特定类型提供定制的实现,以解决这些特殊情况下的需求。

// 例如日期类中的函数模板的使用,在使用指针比较的时候就会出现错误,这时候就需要进行模板特化
template<class T>
bool Less(T left, T right)
{
    return left < right;
}

Date d1(2022, 7, 7);
Date d2(2022, 7, 8);
cout << Less(d1, d2) << endl; // 可以比较,结果正确

Date* p1 = &d1;
Date* p2 = &d2;
cout << Less(p1, p2) << endl; // 可以比较,结果错误

当使用指针进行比较的时候比较的就是指针指向的地址,而地址是从栈上向下申请,所以不会按照原本日期类希望的排序方法进行排序。
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化

函数模板特化

函数模板特化用于为特定类型定制函数实现。它的典型用处是在普通模板无法满足某些类型需求时提供特定的功能。特化函数的签名必须与原模板函数完全一致。

函数模板的特化步骤:

  1. 必须要先有一个基础的函数模板;
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型;
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇
    怪的错误。

使用场景与示例

紧接上面的错误案例:假设我们有一个通用的比较函数模板Less,它比较两个对象的大小

template<typename T>
bool Less(T left, T right) {
    return left < right;
}

上述模板在大多数情况下都能正常工作,但如果传入的是指针类型,那么比较的将是指针的地址而非指向的对象。为了正确比较指针指向的内容,我们需要对指针类型进行特化:

template<>
bool Less<Date*>(Date* left, Date* right) {
    return *left < *right;
}

此特化版本用于比较指针指向的Date对象,确保比较逻辑正确。

函数模板特化的实现细节

在实现函数模板特化时,需要注意以下几点:

  • 特化声明:模板特化的声明需要紧随template<>,然后是函数签名,特化的类型需要放在尖括号中。
  • 参数一致性:特化函数的参数列表必须与原模板函数保持一致,不能增加或减少参数,也不能更改参数的顺序或类型。

**注意:**推荐直接写一个函数实现特殊处理,编译器在处理的时候会优先调用更匹配的。

类模板特化

类模板特化比函数模板特化更加复杂,主要分为全特化偏特化。类模板特化的主要作用是为特定类型提供定制的类定义和实现。

全特化

全特化是指将模板参数列表中的所有参数都具体化(全特化版本中的所有参数都必须指定具体类型)。

示例

假设我们有一个通用的数据存储类模板Data,它可以存储两个不同类型的对象:

template<typename T1, typename T2>
class Data {
public:
    Data() { std::cout << "Data<T1, T2>" << std::endl; }
private:
    T1 _d1;
    T2 _d2;
};

我们可以为特定的类型组合如intchar进行全特化:

template<>
class Data<int, char> {
public:
    Data() { std::cout << "Data<int, char>" << std::endl; }
private:
    int _d1;
    char _d2;
};

在这个全特化版本中,我们为Data类提供了一个int和一个char类型的特化实现。这意味着当我们创建Data<int, char>类型的对象时,将调用特化版本的构造函数。

偏特化

偏特化是部分特化的形式,可以仅对部分模板参数进行特化。偏特化比全特化更灵活,允许特化的同时保留一些模板参数。

偏特化中有两种表现方式:部分特化、通过限制参数进行特化

部分优化

部分特化允许开发者针对特定的模板参数进行特化,而其他模板参数保持泛型(需要在template中声明)。这样可以在不影响通用模板行为的情况下,为某些特定类型或类型组合提供专门的实现。

示例:

template<typename T1, typename T2>
class Pair {
public:
    Pair(T1 first, T2 second) : first_(first), second_(second) {}
    T1 first() const { return first_; }
    T2 second() const { return second_; }

private:
    T1 first_;
    T2 second_;
};

上述模板类是通用的,可以存储任意类型的两个数据。然而,如果我们需要对第一类型是int的情况进行特化,可以使用部分特化:

template<typename T2>
class Pair<int, T2> {
public:
    Pair(int first, T2 second) : first_(first), second_(second) {}
    int first() const { return first_; }
    T2 second() const { return second_; }

    void setFirst(int value) { first_ = value; } // 额外的特化方法

private:
    int first_;
    T2 second_;
};

在这个部分特化版本中,我们特化了Pair模板的第一个类型为int,第二个类型保持泛型。这样,当Pair<int, T2>的对象创建时,将调用这个特化版本,而不是通用版本。

通过进一步限制模板参数进行特化
偏特化为指针类型示例:

当需要模板参数为指针类型的时候,可以对其进行特化,以实现针对于指针的特定逻辑。这在需要对指针执行特定操作(如解引用、比较等)时尤为有用。

// 两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data<T1*, T2*> {
public:
    Data() { std::cout << "Data<T1*, T2*>" << std::endl; }
private:
    T1 _d1;
    T2 _d2;
};
  • 模板特化Data<T1*, T2*>,这个偏特化版本对模板的两个参数T1T2进行了特化,使得它们必须是指针类型。
  • 实现细节:在构造函数中打印了一条消息,标识这是指针特化的版本。
  • 成员变量:特化类中的成员变量依然是T1T2类型,不过它们实际上是指针指向的对象的类型。
偏特化为引用类型示例:

对于引用类型的参数,我们可以通过特化来处理那些需要传递引用的情况。这在需要修改外部对象或避免对象复制时非常有用。

// 两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data<T1&, T2&> {
public:
    Data(const T1& d1, const T2& d2)
        : _d1(d1), _d2(d2) {
        std::cout << "Data<T1&, T2&>" << std::endl;
    }
private:
    const T1& _d1;
    const T2& _d2;
};
  • 模板特化Data<T1&, T2&>,这个偏特化版本对模板的两个参数T1T2进行了特化,使得它们必须是引用类型。
  • 实现细节:在构造函数中接受了T1T2类型的引用,并初始化类的成员变量。
  • 成员变量:特化类中的成员变量是对传入对象的常量引用const T1&const T2&,这确保了数据不会被意外修改。

特化测试结果分析

void test2() {
    Data<double, int> d1; // 调用全特化的模板
    Data<int, double> d2; // 调用基础的模板
    Data<int*, int*> d3; // 调用特化的指针版本
    Data<int&, int&> d4(1, 2); // 调用特化的引用版本
}
  • Data<double, int> d1;:调用了全特化模板,因为参数类型既不是指针也不是引用。
  • Data<int, double> d2;:同样调用全特化模板。
  • Data<int*, int*> d3;:调用了特化的指针版本,因为两个参数都是指针类型。
  • Data<int&, int&> d4(1, 2);:调用了特化的引用版本,因为两个参数是引用类型(注意,这里初始化引用类型参数时传递的是常量12,这些字面量会被隐式转换为合适的引用类型)。

模板特化中的注意事项

实例化时严格的匹配性

模板编程中,模板实例化时的匹配性要求非常严格,即使已经对模板进行了特化,在实例化时也必须精确匹配到最合适的模板版本。这种严格的匹配性体现在以下几个方面:

  • 全特化:指的是为特定类型组合提供一个完全定制化的实现。全特化要求在实例化时完全匹配所有模板参数类型,只有在参数完全匹配时,才会使用该特化版本。
  • 偏特化:允许对部分模板参数进行特化,同时保持其他参数的泛型性。在实例化时,编译器会优先选择最匹配的特化版本。如果没有找到完全匹配的特化版本,编译器才会退而求其次,选择更加通用的版本。
  • 模板匹配顺序:编译器在选择模板实例化时,会按照以下优先顺序进行匹配:
    • 完全匹配的全特化(优先级最高)
    • 最匹配的偏特化
    • 最通用的模板

指针特化时const的修饰问题

为什么在参数列表使用const

防止修改传入的参数:特化版本中的Date* const& leftDate* const& right,通过使用const,函数保证不会修改传入的指针变量本身的值,即指针的指向保持不变。这是一种安全措施,避免函数对外部数据的不必要的修改。

const与指针修饰关系

指针本身是常量 (const*之后)

const放在指针符号*之后时,它修饰的是指针本身,这意味着指针的值(即它指向的内存地址)不能被改变。但指针指向的对象的内容可以改变。

Date* const pDate;

在这个例子中,pDate是一个常量指针,它指向一个Date类型的对象。pDate本身不能指向别处,但是pDate指向的Date对象的内容是可以修改的。

通过特化时将**const**放在在*****之后即可解决在特化中的修饰关系。

指向的内容是常量 (const*前面)

const放在*前面时,它修饰的是指针指向的对象,这意味着不能通过这个指针修改指向的对象的内容,但指针本身可以指向不同的对象。

const Date* pDate;

在这个例子中,pDate是一个指向Date对象的指针。虽然pDate本身可以指向不同的Date对象,但不能通过pDate来修改它所指向的对象的内容。

指针特化时const修饰的应用

通用函数模板

template<class T>
bool LessFunc(const T& left, const T& right)
{
	return left < right;
}

该函数模板中的const修饰的是传入的leftright不会被改变。

特化函数模板

template<>
bool LessFunc<Date*>(Date* const& left, Date* const& right)
{
	return *left < *right;
}

**Date* const& left****Date* const& right**:这两个参数都是指向Date对象的常量指针的引用。这意味着:

  • 指针本身不可改变:函数内部不能改变leftright指向的地址(与通用模板中的修饰目的相同)。

为了保持与通用模板中const效果相同,因此写为Date* const& left。通用模板是为了是传入的数据不被修改,而对于传入的指针来说,**const**放在*****之后,表示指针本身是常量。换句话说,指针本身的地址不能改变,也就是说,一旦初始化后,指针不能指向其他地址,也就是传入的指针不能被修改了,和通用模板实现的效果相同。
因此,Date* const& 的意思是“指向Date对象的常量指针的引用”。这个引用在函数内不会改变其所引用的指针对象,也不能通过引用修改指针本身的指向。

已经特化的类中T表示为什么?

在已经特化过的类中,不管特化时是将原类型特化为指针类型或者引用类型之类的,在类中使用T的时候一律会按照原类型进行使用,也就是说如果在类中要用原类型的指针类型的话,还是需要用T*

如此表示在const修饰传参时也有用处,例如上文所理解的LessFunc<Date*>(Date* const& left, Date* const& right),如果特化为指针的类中Date实际表示为Date*的话,那么在修饰的时候究竟要如何修饰呢。此时就会产生语法与习惯上的矛盾,所以将T直接作为原类型使用会更加方便与顺手。

模板的分离编译

分离编译模式简介

分离编译是软件工程中的一个基本概念,它指的是将源代码分割成多个模块,每个模块独立编译,最后通过链接器将这些模块组合成最终的可执行文件。这种方式提高了编译的并行性,同时也使得代码维护更加简单,因为修改一个模块通常不会影响到其他模块的编译。

模板的分离编译

分离编译测试

我们有一个模板函数Add,它的声明和定义被分别放在不同的文件中:

// a.h
template<class T>
T Add(const T& left, const T& right);

// a.cpp
template<class T>
T Add(const T& left, const T& right)
{
    return left + right;
}

main.cpp中,我们使用了Add函数的两个实例:

#include "a.h"

int main()
{
    Add(1, 2); // 整数实例
    Add(1.0, 2.0); // 浮点数实例
    return 0;
}

此时当运行的时候会出现链接错误。

原因解析

C/C++程序的编译链接原理

C/C++程序的构建过程通常分为四个阶段:预处理、编译、汇编和链接。

  1. 预处理:预处理器处理#include指令和其他预处理器指令,将头文件的内容插入到源文件中,同时处理宏定义等。
  2. 编译:编译器将预处理后的源代码转换成汇编代码。在这个阶段,编译器检查语法、词法和语义错误,并且如果一切正确,将代码转换成机器可以理解的指令集。
  3. 汇编:将汇编代码转换为机器代码的二进制形式。
  4. 链接:链接器将多个目标文件(.obj)和库文件链接起来,解决符号引用问题,生成最终的可执行文件。

image.png

为什么不能分离定义?

**原因:**模板实例化的代码并不是编译的时候在模板位置直接生成的,而是在需要实例化的时候才会生成特定的具体代码。

  • 实例化时机:模板的实例化发生在编译器遇到模板函数或类的使用时。如果模板的定义不在编译器当前正在处理的编译单元中,那么编译器无法知道如何实例化模板,因此不会生成相应的函数代码。
  • 地址问题:如你提到的例子,当在a.cpp中没有Add模板的具体实例化代码时,编译器不会生成对应的函数。而在main.obj中尝试使用Add<int>Add<double>时,链接器会在链接阶段寻找这些函数的地址,但因为它们在编译时没有被生成,所以链接器找不到这些地址,导致链接错误。
  • 单定义规则(One Definition Rule,ODR):C++的单定义规则要求每个非内联函数或变量在一个程序中只能有一个定义。模板的每次实例化都被视为一个独立的函数或类型定义,这意味着每次实例化都必须在同一个编译单元中完成,否则可能会违反ODR。
  • **推荐做法:**将模板的声明和定义放在同一个头文件中,确保在任何包含该头文件的编译单元中都可以进行正确的实例化。


image.png

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

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

相关文章

红酒与午后:悠闲时光的惬意选择

阳光透过窗棂&#xff0c;轻轻洒在木质的茶几上&#xff0c;斑驳的光影交织出一幅静谧而温暖的画面。在这宁静的午后&#xff0c;一瓶洒派红酒&#xff08;Bold & Generous&#xff09;静静地摆放在那里&#xff0c;仿佛正等待着与你一同开启一段悠闲的品酒时光。 一、午后…

【往届均已完成EI、SCOPUS检索】第四届电气工程与计算机技术国际学术会议(ICEECT 2024,9月27-29)

第四届电气工程与计算机技术国际学术会议&#xff08;ICEECT2024&#xff09;将于9月27日-29日在哈尔滨举办。 会议主要围绕"电路与系统"、“电气工程材料”、“计算机视觉”、“计算机技术”等专业研究领域展开讨论。旨在为气工程、计算机技术等领域的专家学者及企业…

Figma汉化教程

Figma汉化教程&#xff0c;需要 5 步 第一步&#xff1a;我们打开一个Figma中文社区网站 https://www.figma.cool/cn。我们点击左上角的Figma汉化&#xff0c;进入Figma汉化安装的页面。 第二步&#xff1a;在Figma 软件汉化页面中&#xff0c;选择谷歌浏览器汉化&#xff0c;点…

Litestar GET function blocks OpenAI

题意&#xff1a;Litestar GET 函数阻塞 OpenAI 问题背景&#xff1a; When I transfer function to litestar, it suddenly stops OpenAI from returning a completion. I can print to console every declared variable except answer: 当我将函数传递给 litestar 时&#…

解析蚂蚁T21 190T 算力与能效的新突破

蚂蚁T21 190T 的参数如下&#xff1a; ● 产生币种&#xff1a;B & T & C ● 额定算力&#xff1a;190T ● 额定功耗&#xff1a;3610W ● 功耗比&#xff1a;19.0J/T ● 额定电压&#xff1a;380~415V ● 芯片参数&#xff1a;采用全新的5nm芯片技术&#xff08…

Linux笔记 --- Linux内核链表

Linux 内核链表 经过上一小节的分析&#xff0c;我们知道了传统链表的先天缺陷&#xff1a;没有将具体的数据从组织这些数据的逻辑结构中剥离&#xff0c;而Linux内核链表的思路&#xff0c;正是从一方面着手&#xff0c;追根溯源直抵病灶&#xff0c;彻底颠覆了传统链表&…

Linux内网环境部署thingsboard(离线部署)

先说明一下内网部署的环境,我这里是安装的thingsboard3.6.4 下面所有环境包的版本都是基于这个版本 我们需要安装jdk11,postgres数据库&#xff0c;这里注意下jdk必须使用rpm方式安装&#xff0c;要不后面安装Thingsboard会提示你没有检测到jdk. 下面我们就一步一步来 1.先下…

同城货运软件开发货运搬家系统源码基于Java开发的货运平台

一.管理端配置及操作 1.服务配置 添加:服务,给服务添加车型和车厢,以及收费金额 2.用户中心 分为普通用户 师傅用户 和推广员用户; 推广员用户的一二级分佣不为0,可给推广员设置一二级佣金 3.车厢/车型管理 给服务添加车厢 和车型,选择 服务后,只能选择该服务关联的车型和…

Tomato靶机~文件包含日志

寻找网站上传点并把 php 恶意代码文件改成 jpg 上传到网站上在本地包含引入恶意代码&#xff0c;当文件被引入后代码就被执行&#xff1b; 0x01信息收集 # 环境准备&#xff1a; Target IP&#xff1a;192.168.66.143 Attack IP&#xff1a;192.168.66.84 靶机目标&#xff1…

无缝协作的艺术:Codigger 视频会议(Meeting)的用户体验

在当今数字化的时代&#xff0c;远程协作已经成为工作和学习中不可或缺的一部分。然而&#xff0c;远程协作也面临着诸多挑战&#xff0c;如沟通不畅、信息同步不及时、协作工具的复杂性等。而 Codigger 视频会议&#xff08;Meeting&#xff09;作为一款创新的工具&#xff0c…

伦敦银和伦敦金的关系是怎么样的?

在贵金属投资市场中&#xff0c;有两个品种是经常被投资者讨论的&#xff0c;一个是伦敦银&#xff0c;而另外一个是伦敦金&#xff0c;他们的名字很相似&#xff0c;那实际上他们有何关系呢&#xff1f;下面我们就来简单地讨论一下。 伦敦银其实也叫国际现货白银&#xff0c;是…

探索3D视觉中的Transformer架构:通用Backbone与自适应采样策略 !

1 Introduction 计算机视觉中的一个基本问题是在三维空间中理解和识别场景与物体。它允许以紧凑的方式表达关系&#xff0c;并提供在现实世界中导航和操作的能力。3D视觉在各个领域都发挥着重要作用&#xff0c;包括自动驾驶、机器人技术、遥感、医疗、增强现实、设计行业等众…

【Nuxt】初识 Nuxt 和目录说明

初识 Nuxt Nuxt3 支持 Vue3 及其周边生态&#xff0c;提供前后端功能&#xff0c;支持 CSR(SPA)&#xff0c;SSR&#xff0c;SSG 渲染模式的应用。 Nuxt3 特点&#xff1a; Vue技术栈 Nuxt3是基于Vue3Vue RouterVite等技术栈&#xff0c;全程Vue3Vite开发体验(Fast)。 自动导…

6大类果蔬食物百科大全ACCESS数据库

其实今天这个数据库早些时候就已经搞到了&#xff0c;但是鉴于它的多表结构不太喜欢就一直没有整理&#xff0c;然而现在仔细看起来&#xff0c;又觉得这种安排好处还是很好的&#xff0c;清晰明了。发上来看看有没有朋友喜欢&#xff0c;包含了水果类、蔬菜类、坚果类、肉类、…

xss漏洞(二,xss靶场搭建以及简单利用)

本文仅作为学习参考使用&#xff0c;本文作者对任何使用本文进行渗透攻击破坏不负任何责任。 一&#xff0c;环境搭建。 使用工具&#xff1a;PHP study&#xff0c;dvwa靶场。 1&#xff0c;GitHub上下载dvwa到PHP study的WWW文件夹内&#xff0c;并解压。 dvwa下载地址 …

3D Web轻量引擎HOOPS Communicator处理大模型时有哪些优化技巧?

在当今的工程设计和可视化领域&#xff0c;大型3D模型变得越来越普遍。然而&#xff0c;这些模型的复杂性和体量给传统的渲染技术带来了挑战。HOOPS Communicator&#xff0c;作为一个高性能的3D可视化工具&#xff0c;提供了一系列的优化技巧&#xff0c;以提高大型模型的渲染…

ARMxy工业计算机支持BACnet推动智能楼宇能源管控升级

城市化进程的加速&#xff0c;智能楼宇成为了未来建筑的发展趋势。而能源管控作为智能楼宇的重要组成部分&#xff0c;对于实现节能减排、提高能源利用效率具有至关重要的意义。ARMxy 工业计算机作为一种高性能、低功耗的计算平台&#xff0c;为智能楼宇能源管控提供了全新的解…

【砖墙】python刷题记录

R4-哈希表 这题不就是射箭那道题&#xff1f;&#xff01; 很类似好吧 【用最少数量的箭引爆气球】python刷题记录 哈希表前缀和秒杀&#xff01; class Solution:def leastBricks(self, wall: List[List[int]]) -> int:dictdefaultdict(int)nlen(wall)for i in range(n)…

酒店民宿小程序搭建,用户在线一键预订!

近几年&#xff0c;我国旅游业发展非常迅速&#xff0c;同时也带动了酒店民宿的发展。因此&#xff0c;为了提升游客的住宿体验&#xff0c;各大酒店民宿也纷纷开始发展专属的预约小程序&#xff0c;让用户可以在小程序上更加快速便利的查看酒店信息&#xff0c;预订心仪的房间…

中小微企业必看:税贷票贷融资策略与实战技巧

各位中小微企业的老板们&#xff0c;今天咱们来聊聊个实用话题——税贷和票贷&#xff0c;少部分朋友可能还觉得挺新鲜。别看它们听起来有点专业&#xff0c;其实搞懂了&#xff0c;融资就能变得简单又高效。 一、啥是税贷票贷&#xff1f; 税贷票贷&#xff0c;说白了&#x…