C++模板的多文件编程与显式实例化

模板的多文件编程

  我们常将函数定义放在源文件中,将函数声明放在头文件中。编译是针对单个源文件的,只要有函数声明,编译器就能知道函数调用是否正确;而将函数调用和函数定义对应起来的过程,可以延迟到链接时期。正是有了链接器的存在,函数声明和函数定义的分离才得以实现。

  初学者往往也会将模板的声明和定义分散到不同的文件中,但这种做法是不对的,应该将模板的声明和定义都放在头文件中

  原因:模板的实例化是由编译器完成的,而不是由链接器完成的,这可能会导致在链接期间找不到对应的实例。

模板的显式实例化

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

函数模板的显式实例化

显式实例化的例子

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

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

也可在函数调用的源文件中再增加一条语句

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;
}

类模板的显式实例化

针对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;
}

显式实例化的缺陷

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值