模板使用&&模板为什么不能分离编译

模板

为什么要模板,其实目的就是一个,
就是将本来应该我们做的重复的事情交给编译器

模板的基本用法

模板函数

用法用例

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

上面实现了一个两个类型交互的函数,利用模板函数,我们可以传入类型完成不同类型值的交换,例如

int a;
int b;
swap<int>(a,b);

float f1;
float f2;
swap<float>(f1,f2);

隐式实例化

编译器根据传入实参推演模板参数的实际类型

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

int main()
{
 int a;
 int b;
swap(a,b);
}

显式实例化

在函数名后的<>中指定模板参数的实际类型

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

int main()
{
 int a;
 int b;
swap<int>(a,b);
}

模板参数的匹配原则

(1)一个非模板函数可以和一个同名的模板函数同时存在,而且该函数的模板还可以被实例化为这个非模板函数

void swap(int& a,int& b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int a=10;
    int b=20;
    swap(a,b);//编译器优先调用可以匹配的非模板参数
    swap<int>(a,b);//主动让编译器生成swap的实例
}

(2)对于非模板函数的同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好的函数,那么将选择模板

void swap(int& a,int& b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

template<typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int a=10;
    int b=20;
    float f1;
    float f2;
    swap(a,b);//编译器优先调用可以匹配的非模板参数
    swap(f1,f2);//编译器会生成一个更加匹配的版本,编译器根据实参生成更加匹配的add函数
}

(3)模板函数不允许自动类型转换,但普通函数可以进行自动类型转换.

模板定义的格式

类模板

template<class T1,class T2,..,clas Tn>
class 模板类名
{
    //类内成员定义
}

类外的函数定义需要加上模板参数列表
template<class T>
class Vector
{
    ...
}
template <class T>
vector<T>::~vector()
{
 ...   
}

模板的实例化

我们定义的模板函数其实不是真正调用的函数,我们真正使用的函数是通过模板函数实例化出来的代码

非类型模板参数

上面的模板是传类型,而事实上,模板还可以传非类型的参数,例如下面

示例代码

#include <iostream>

template<class T,size_t N = 10>
class Array
{
  public:
  T _array[N]; 
};

int main()
{
  Array<int,10> arr1; 
  Array<int,20> arr2;
  return 0;
}

注意

(1)浮点数,类对象以及字符串是不允许作为非类型模板参数的
(2)非类型的模板参数必须在编译期就能确认结果。就和数组长度的定义一样

模板的特化

模板可以实现与类型无关的代码,但是也会有一些地方需要特殊处理,例如

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

void Test()
{
char* p1 = "hello";
char* p2 = "world";
if(IsEqual(p1, p2))
cout<<p1<<endl;
else
cout<<p2<<endl;
}

对于字符串的比较就不能直接像上面这样比较,而需要特殊化处理

模板特化的步骤

(1)必须有一个基础的模板

(2)关键template后面接一对空的尖括号<>

(3)函数名后跟一对尖括号,尖括号指定需要特化的类型

(4) 函数形参表必须和函数模板的基础参数类型完全相同,如果不同就会报错

template<>
bool IsEqual<char*>(char*& left, char*& right)
{
if(strcmp(left, right) > 0)
    return true;
return false;
}

注意

也可以直接实现该函数,直接匹配最佳的非模板参数,那样就不需要编译器再生成一个实例了

bool IsEqual(char* left, char* right)
{
if(strcmp(left, right) > 0)
return true;
return false;
}

利用非类型模板参数的特性解题

(1)求1+2+3+…n,要求不能使用乘法除法,for,while,if,else,switch,case等关键字及条件判断语句(A?B:C)**

解题思路

利用非类型参数做一个递归的实例化,利用特化做一个递归的收敛

代码

模板的偏特化

部分特化

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

// 将第二个参数特化为int
template <class T1>
class Data<T1, int>
{
    public:
        Data() {cout<<"Data<T1, int>" <<endl;}
    private:
        T1 _d1;
        int _d2;
};

对参数进行限制

//两个参数偏特化为指针类型
template <typename T1, typename T2>
class Data <T1*, T2*>
{
    public:
      Data() {cout<<"Data<T1*, T2*>" <<endl;}
    private:
        T1 _d1;
        T2 _d2;
};


//两个参数偏特化为引用类型
template <typename T1, typename T2>
class Data <T1&, T2&>
{
    public:
        Data(const T1& d1, const T2& d2)
        : _d1(d1)
        , _d2(d2)
        {
        cout<<"Data<T1&, T2&>" <<endl;
        }
    private:
        const T1 & _d1;
        const T2 & _d2;
};

模板特化的应用

类型萃取

为什么要类型萃取呢?看下面的代码

template<class T>
void Copy(T* dst, const T* src, size_t size)
{
    memcpy(dst, src, sizeof(T)*size);
}
int main()
{
    // 试试下面的代码
    string strarr1[3] = {"11", "22", "33"};
    string strarr2[3];
    Copy(strarr2, strarr1, 3);
}

显然对字符串的拷贝,上面的代码并不通用 ,我们可以尝试用一个方法区别自定义类型和内置类型,比如

template<class T>
void Copy(T* dst, const T* src, size_t size, bool IsPODType)
{
    if(IsPODType)
        memcpy(dst, src, sizeof(T)*size);
    else
    {
        for(size_t i = 0; i < size; ++i)
        dst[i] = src[i];
    }
}

上面增加了一个 bool 来区别自定义类型和内置类型,但是这样就增加了使用上出错的概率,能否用一种方法让编译器自动识别呢,如下

//
// POD: plain old data 平凡类型(无关痛痒的类型)--基本类型
// 指在C++ 中与 C兼容的类型,可以按照 C 的方式处理。
//
// 此处只是举例,只列出个别类型
bool IsPODType(const char* strType)
{
    const char* arrType[] = {"char", "short", "int", "long", "long long", "float",
    "double", "long double"};
    for(size_t i = 0; i < sizeof(array)/sizeof(array[0]); ++i)
    {
        if(0 == strcmp(strType, arrType[i]))
        return true;
    }
        return false;
}
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
    if(IsPODType(typeid(T).name()))
        memcpy(dst, src, sizeof(T)*size);
    else
    {
        for(size_t i = 0; i < size; ++i)
        dst[i] = src[i];
    }
}

上面利用tipeid来确认拷贝对象的实际类型,在内置类型的集合中寻找是否匹配内置类型,但是这样就有缺陷了,每次都要比较字符串,效率很低

事实上我们可以借助模板的特化进行类型萃取,而且是编译的时候就已经完成了,所以这方法效率很高

/*
T为int:TypeTraits<int>已经特化过,程序运行时就会使用已经特化过的TypeTraits<int>, 该类中的
IsPODType刚好为类TrueType,而TrueType中Get函数返回true,内置类型使用memcpy方式拷贝
T为string:TypeTraits<string>没有特化过,程序运行时使用TypeTraits类模板, 该类模板中的IsPODType
刚
好为类FalseType,而FalseType中Get函数返回true,自定义类型使用赋值方式拷贝
*/
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
    if(TypeTraits<T>::IsPODType::Get())
      memcpy(dst, src, sizeof(T)*size);
    else
    {
        for(size_t i = 0; i < size; ++i)
        dst[i] = src[i];
    }
}
int main()
{
    int a1[] = {1,2,3,4,5,6,7,8,9,0};
    int a2[10];
    Copy(a2, a1, 10);
    string s1[] = {"1111", "2222", "3333", "4444"};
    string s2[4];
    Copy(s2, s1, 4);
    return 0;
}

关于为什么模板不能分离编译?

1.为什么模板不能分离编译了,首先我们要这样想,模板是一个图纸一样的东西,假设我们的模板参数是一个int,那编译器就要借助这个模板生成一个为int类型服务的代码,也就是生成一个模板实例,而这个实例是一段代码。

2.模板生成实例的时候是在链接前,生成实例的前提是当前文件中调用了模板

3.C/C++程序要运行,一般要经历一下步骤

预编译—》编译—>汇编—> 链接

编译:对程序按照语言特性进行词法,语法,语义分析,错误检查无误后生成汇编代码,注意头文件不参与编译,编译器对工程中的多个源文件是分离开单独编译的。

链接:将多个obj文件和合并成一个,并处理没有解决的地址问题。

下面代码引用自 https://blog.csdn.net/pongba/article/details/19130

//-------------test.h----------------//

template<class T>

class A

{

public:

void f(); // 这里只是个声明

};




//---------------test.cpp-------------//

#include”test.h”

template<class T>

void A<T>::f()// 模板的实现

{

 …//do something

}




//---------------main.cpp---------------//

#include”test.h”

int main()

{

A<int> a;

f(); // #1

}

编译器在编译 #1的时候并不知道A::f的定义,因为它不在test.h里面,于是编译器只好寄希望于连接器,希望它能够在其他的.obj里面找到A::f的实例,在本例中就是test.obj,然而,实际上后者根本就没有A::f的二进制代码

因为C++标准明确表示,当一个模板不被用到的时候它就不该被实例化出来,test.cpp中用到了A::f了吗?没有!!所以实际上test.cpp编译出来的test.obj文件中关于A::f一行的二进制代码也没有,于是连接器就傻了,根本就没有要的二进制代码嘛,于是就报出了链接错误

如果在test.cpp中写一个函数,然后里面调用了A::f 编译器就会把它实例化出来,因为test.cpp使用了A::f,并且test.cpp也有A::f的模板,所以就会实例化出来,在这种情况下,我们再链接,就发现可以链接上了。

下面是在test.cpp中写一个函数,函数中调用A::f,这样就会生成A::f的实例,test.obj的符号导出表中就有了A::f这个符号的地址,于是连接器就能完成任务。

如下

//---------test.h------//
template<class T>
int Add(const T& d1,const T& d2);

//---------test.cpp-----//
#include "test.h"

template<class T>
int Add(const T& d1,const T& d2)
{
  return d1+d2;
}


void fun()
{
  Add<int>(1,2);
}


//-------main.cpp---//
#include <iostream>
#include "test.h"

using namespace std;

int main()
{
    cout<<Add<int>(1,2)<<endl;
  return 0;
}

//------makefile---//
main:main.cpp  test.cpp
	g++ -o $@ $^


加上void fun()
{
Add(1,2);
}后就可以编译成功了,同时要这样编译g++ -o main main.cpp test.cpp

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章