首页 > 编程语言 >C++模板的显式实例化

C++模板的显式实例化

时间:2024-03-16 14:22:56浏览次数:23  
标签:void C++ 实例 Swap template 显式 模板

C++ 模板

前面讲到的模板的实例化是在调用函数或者创建对象时由编译器自动完成的,不需要程序员引导,因此称为隐式实例化。相对应的,我们也可以通过代码明确地告诉编译器需要针对哪个类型进行实例化,这称为显式实例化。

编译器在实例化的过程中需要知道模板的所有细节:对于函数模板,也就是函数定义;对于类模板,需要同时知道类声明和类定义。我们必须将显式实例化的代码放在包含了模板定义的源文件中,而不是仅仅包含了模板声明的头文件中。

显式实例化的一个好处是,可以将模板的声明和定义(实现)分散到不同的文件中了。

函数模板的显式实例化

以上节讲到的 compare() 函数为例,针对 double 类型的显式实例化代码为:

template void Swap(double &a, double &b);

这条语言由两部分组成,前边是一个template关键字(后面不带< >),后面是一个普通的函数原型,组合在一起的意思是:将模板实例化成和函数原型对应的一个具体版本。

将该代码放到 func.cpp 文件的最后,再运行程序就不会出错了。

另外,还可以在包含了函数调用的源文件(main.cpp)中再增加下面的一条语句:

extern template void Swap(double &a, double &b);

该语句在前面增加了extern关键字,它的作用是明确地告诉编译器,该版本的函数实例在其他文件中,请在链接期间查找。不过这条语句是多余的,即使不写,编译器发现当前文件中没有对应的模板定义,也会自动去其他文件中查找。

上节我们展示了一个反面教材,告诉大家不能把函数模板的声明和定义分散到不同的文件中,但是现在有了显式实例化,这一点就可以做到了,下面就对上节的代码进行修复。
func.cpp 源码:

//交换两个数的值
template<typename T> void Swap(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

//冒泡排序算法
void bubble_sort(int arr[], int n)
{
    for(int i=0; i<n-1; i++)
    {
        bool isSorted = true;
        for(int j=0; j<n-1-i; j++)
        {
            if(arr[j] > arr[j+1])
            {
                isSorted = false;
                Swap(arr[j], arr[j+1]);  //调用Swap()函数
            }
        }
        if(isSorted) break;
    }
}

template void Swap(double &a, double &b);  //显式实例化定义

func.h 源码:

#ifndef _FUNC_H
#define _FUNC_H

template<typename T> void Swap(T &a, T &b);
void bubble_sort(int arr[], int n);

#endif

main.cpp 源码:

#include <iostream>
#include "func.h"
using namespace std;

//显示实例化声明(也可以不写)
extern template void Swap(double &a, double &b);
extern template void Swap(int &a, int &b);

int main()
{
    int n1 = 10, n2 = 20;
    Swap(n1, n2);
  
    double f1 = 23.8, f2 = 92.6;
    Swap(f1, f2);

    return 0;
}

显式实例化也包括声明和定义,定义要放在模板定义(实现)所在的源文件,声明要放在模板声明所在的头文件(当然也可以不写)。

类模板的显式实例化

类模板的显式实例化和函数模板类似。以上节的 Point 类为例,针对char*类型的显式实例化(定义形式)代码为:

template class Point<char*, char*>;

相应地,它的声明形式为:

extern template class Point<char*, char*>;

不管是声明还是定义,都要带上class关键字,以表明这是针对类模板的。

另外需要注意的是,显式实例化一个类模板时,会一次性实例化该类的所有成员,包括成员变量和成员函数。

有了类模板的显式实例化,就可以将类模板的声明和定义分散到不同的文件中了,下面我们就来修复上节的代码。
point.cpp 源文件:

#include <iostream>
#include "point.h"
using namespace std;

template<class T1, class T2>
void Point<T1, T2>::display() const
{
    cout<<"x="<<m_x<<", y="<<m_y<<endl;
}

//显式实例化定义
template class Point<char*, char*>;
template class Point<int, int>;

point.h 源码:

#ifndef _POINT_H
#define _POINT_H

template<class T1, class T2>
class Point
{
public:
    Point(T1 x, T2 y): m_x(x), m_y(y){ }
public:
    T1 getX() const{ return m_x; }
    void setX(T1 x){ m_x = x; }
    T2 getY() const{ return m_y; };
    void setY(T2 y){ m_y = y; };
    void display() const;
private:
    T1 m_x;
    T2 m_y;
};

#endif

main.cpp 源码:

#include <iostream>
#include "point.h"
using namespace std;

//显式实例化声明(也可以不写)
extern template class Point<char*, char*>;
extern template class Point<int, int>;

int main()
{
    Point<int, int> p1(10, 20);
    p1.setX(40);
    p1.setY(50);
    cout<<"x="<<p1.getX()<<", y="<<p1.getY()<<endl;

    Point<char*, char*> p2("东京180度", "北纬210度");
    p2.display();

    return 0;
}

总结

函数模板和类模板的实例化语法是类似的,我们不妨对它们做一下总结:

extern template declaration;  //实例化声明
template declaration;  //实例化定义

对于函数模板来说,declaration 就是一个函数原型;对于类模板来说,declaration 就是一个类声明。

显式实例化的缺陷

C++ 支持显式实例化的目的是为「模块化编程」提供一种解决方案,这种方案虽然有效,但是也有明显的缺陷:程序员必须要在模板的定义文件(实现文件)中对所有使用到的类型进行实例化。这就意味着,每次更改了模板使用文件(调用函数模板的文件,或者通过类模板创建对象的文件),也要相应地更改模板定义文件,以增加对新类型的实例化,或者删除无用类型的实例化。

一个模板可能会在多个文件中使用到,要保持这些文件的同步更新是非常困难的。而对于库的开发者来说,他不能提前假设用户会使用哪些类型,所以根本就无法使用显式实例化,只能将模板的声明和定义(实现)全部放到头文件中;C++ 标准库几乎都是用模板来实现的,这些模板的代码也都位于头文件中。

总起来说,如果我们开发的模板只有我们自己使用,那也可以勉强使用显式实例化;如果希望让其他人使用(例如库、组件等),那只能将模板的声明和定义都放到头文件中了。

标签:void,C++,实例,Swap,template,显式,模板
From: https://www.cnblogs.com/uacs2024/p/18077034

相关文章

  • 将C++模板应用于多文件编程
    C++模板在将函数应用于多文件编程时,我们通常是将函数定义放在源文件(.cpp文件)中,将函数声明放在头文件(.h文件)中,使用函数时引入(#include命令)对应的头文件即可。编译是针对单个源文件的,只要有函数声明,编译器就能知道函数调用是否正确;而将函数调用和函数定义对应起来的过程,可以延迟到......
  • 邻接表存储带权的无向图(c++题解)
    题目描述给出一个无向带权图,顶点数为n,边数为m。输入格式第一行两个整数n,m,接下来有m行,每行3个整数u,v,w,表示点u到点v有一条边,边权为w。输出格式第i行输出第点i的所有邻接点,按照点i到该点的边权由小到大输出,如果边权相等,则按照点的编号有小到大输出。样例样例输入复......
  • 有向图的DFS(c++题解)
    题目描述给定一个有向图(不一定连通),有N个顶点,M条边,顶点从1..N依次编号,求出字典序最小的深度优先搜索顺序。输入格式第1行:2个整数,N(1≤N≤200)和M(2≤M≤5000)接下来M行,每行2个整数I,J,描述一条边从顶点I指向顶点J输出格式仅一行,一个顶点编号序列,表示字典序最小的深度优先搜索......
  • C++模板的实例化
    C++模板模板并不是真正的函数或类,它仅仅是编译器用来生成函数或类的一张“图纸”。模板不会占用内存,最终生成的函数或者类才会占用内存。由模板生成函数或类的过程叫做模板的实例化,相应地,针对某个类型生成的特定版本的函数或类叫做模板的一个实例。在学习模板以前,如果想针对不同......
  • C++模板中的非类型参数
    C++模板模板是一种泛型技术,目的是将数据的类型参数化,以增强C++语言(强类型语言)的灵活性。C++对模板的支持非常自由,模板中除了可以包含类型参数,还可以包含非类型参数,例如:template<typename T, int N> class Demo{ };template<class T, int N> void func(T (&arr)......
  • C++示例:学习C++标准库,std::unordered_map无序关联容器的使用
    01std::unordered_map介绍std::unordered_map是C++标准库中的一种无序关联容器模板类,它提供了一种将键映射到值的方法。它的底层基于哈希表实现,内容是无序的,可以在平均情况下在O(1)的时间复杂度内完成插入、查找和删除操作。值得注意的是,哈希表可能存在冲突,即不同的键值......
  • 滴水逆向笔记系列-c++总结2-36.权限控制-37.虚函数-38.多态_绑定
    第三十六课c++3权限控制1.定义和实现分开写2.private和publicprivate权限说明私有变量在类外是无法访问的,只有在类内或者使用类内函数访问类内函数访问3.private真的不能访问吗反汇编看看t对象在初始化public和private成员时都是一视同仁的,在底层还是没区别,都是编......
  • 滴水逆向笔记系列-c++总结3-39.模板-40.引用_友元_运算符重载
    第三十八课c++6模板1.冒泡排序和折半查找voidSort(int*arr,intnLength) { inti; intk; for(i=0;i<nLength-1;i++) { for(k=0;k<nLength-1-i;k++) { if(arr[k]>arr[k+1]) { inttemp=arr[k]; a......
  • 滴水逆向笔记系列-c++总结4-41.new-delete-vector-42.链表
    第四十课c++8new-delete-vector1.内存空间复习在类外函数外的变量就是全局变量,程序一编译地址就已经确定了的临时数据,参数和局部变量就是在堆栈里而使用malloc函数动态申请的则是在堆里2.跟踪调试反汇编函数我们调用malloc函数申请内存,但是不是malloc一个函数完成整个......
  • C++性能分析工具
    gprof:这是一个GNU的性能分析工具,主要用于分析程序的函数调用关系,以及每个函数的运行时间等。Valgrind:这是一个用于内存调试、内存泄漏检测以及性能分析的开源工具集。其中,Valgrind的Callgrind工具可以收集程序运行时的函数调用信息,用于性能分析。perf:这是Linux下的一个性能分析......