跳到主要内容

泛型编程与模板

泛型编程

泛型编程(Generic Programming)是一种语言机制,通过它可以实现一个标准的容器库

像类一样,泛型也是一种抽象数据类型,但是泛型不属于面向对象,它是面向对象的补充和发展

  • 泛型编程在C++中的主要实现为函数模板和模板类 template

函数模板

  • 把处理不同类型的公共逻辑抽象成函数,就得到了函数模板

普通函数模板

假设需要定义一个函数,用来交换两个变量的值,但是输入的变量暂时不确定是什么类型,那我们需要进行如下定义

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

void swap(float &a, float &b) {
/* Code */
}

void swap(double &a, double &b) {
/* Code */
}
//...

由于输入的变量类型并不确定,需要针对不同的变量类型对函数进行重载,将会使代码变得冗长

如果我们使用模板,就可以方便的实现

// template<class T>
template<typename T>
void swap(T &a, T &b) {
T tmp{a};
a = b;
b = tmp;
}
  • template<typename T>将T定义为任意数据类型,在模板函数调用的时候才会被确定
    • 这个数据类型由typename关键字引导,也可以使用class关键字来引导
    • 如果使用到模板的嵌套的话,就必须使用typename
    • 为了避免一些阅读上的歧义,更推荐typename

成员函数模板

成员函数模板的实现与普通函数模板没有什么区别

class Printer {
public:
template<typename T>
void print(const T& t) {
cout << t <<endl;
}
};

需要注意的是

  • 成员函数模板不能是virtual虚函数
  • 成员函数模板不能有缺省参数

函数模板重载

函数模板之间,函数模板与普通函数之间都可以重载。编译器会根据调用时提供的函数参数,调用能够处理这一类型的最特殊的版本

在特殊性上一般按照如下顺序考虑

  1. 普通函数
  2. 特殊模板(限定了T的形式的,指针、引用、容器等)
  3. 普通模板(对T没有任何限制的)
template<typename T>
void func(T &t) { //通用模板函数
cout << "In generic version template " << t << endl;
}

template<typename T>
void func(T *t) { //指针版本
cout << "In pointer version template "<< *t << endl;
}

void func(string *s) { //普通函数
cout << "In normal function " << *s << endl;
}

int main() {
int i = 10;
func(i); //调用通用版本,其他函数或者无法实例化或者不匹配
func(&i); //调用指针版本,编译器选择最特殊的版本
string s = "abc";
func(&s); //调用普通函数,编译器选择最特化的版本
func<>(&s); //调用指针版本,通过<>告诉编译器需要用 template

return 0;
}

模板函数的特化

有时通用的函数模板不能解决个别类型的问题,我们可以使用函数模板的特化进行特殊处理

  • 函数模板特化必须将所有的模板参数全部指定

例如

template<>
void func(int i) {
// code
}
提示

虽然有模板特化的处理方法,但是更多时候更推荐使用重载来解决这样的问题