在C++编程中,模板是实现泛型编程的核心机制。所谓泛型编程,就是编写与数据类型无关的代码,让算法和数据结构能够适用于多种不同的数据类型。这种编程范式可以显著提高代码的复用性,减少重复代码的编写。
模板主要分为两种类型:函数模板和类模板。函数模板允许我们定义一个通用的函数框架,其中的数据类型可以作为参数传递。例如,一个通用的swap函数模板可以用于交换任何数据类型的值:
cpp复制template <typename T>
void swap(T &a, T &b) {
T temp = a;
a = b;
b = temp;
}
在这个例子中,typename T(或等价的class T)声明了一个类型参数T,它将在函数被调用时被实际的数据类型替换。这种机制使得我们不需要为每种数据类型都编写一个单独的交换函数。
类模板则允许我们定义通用的类结构。例如,一个通用的rational(有理数)类模板可以用于创建不同精度的分数:
cpp复制template <typename T>
class rational {
public:
rational(T n, T d = 1) : num(n), denom(d) {}
// 其他成员函数...
private:
T num, denom;
};
通过这种方式,我们可以创建rational<int>、rational<long>等不同精度的有理数类型,而无需为每种精度都编写一个单独的类。
提示:在模板参数列表中,
typename和class关键字可以互换使用,它们在这里的含义完全相同。虽然class可能让人误以为只能用于类类型,但实际上它可以用于任何数据类型。
函数模板的定义以template关键字开始,后跟尖括号<>包围的模板参数列表。每个模板参数前面都需要加上typename或class关键字。例如:
cpp复制template <typename T>
T max(T a, T b) {
return (a > b) ? a : b;
}
这个max函数模板可以用于比较任何支持>操作符的数据类型。当我们需要使用这个模板时,编译器会根据传递的参数类型自动实例化相应的函数:
cpp复制int i = max(5, 10); // 调用max<int>
double d = max(3.14, 2.71); // 调用max<double>
C++编译器具有强大的模板参数推导能力。当调用函数模板时,编译器会检查传递的实参类型,并尝试推导出最合适的模板参数类型。例如:
cpp复制template <typename T>
void print(const T& value) {
std::cout << value << std::endl;
}
print(42); // T被推导为int
print("Hello"); // T被推导为const char*
在某些情况下,我们可能需要显式指定模板参数,特别是当模板参数无法从函数参数中推导出来时:
cpp复制template <typename T>
T create() {
return T();
}
int x = create<int>(); // 必须显式指定T的类型
函数模板可以像普通函数一样被重载。我们可以定义多个同名的函数模板,只要它们的参数列表不同:
cpp复制template <typename T>
void process(T value); // #1
template <typename T>
void process(T* pointer); // #2
template <typename T, int N>
void process(T (&array)[N]); // #3
当调用process函数时,编译器会选择最匹配的模板版本。此外,我们还可以为特定的类型提供模板特化:
cpp复制template <>
void process<std::string>(std::string str) {
// 对string类型的特殊处理
}
注意:函数模板特化应该谨慎使用,因为它可能导致代码难以理解和维护。在大多数情况下,函数重载是更好的选择。
类模板允许我们定义通用的类结构,其中的数据类型可以作为模板参数。一个典型的类模板定义如下:
cpp复制template <typename T>
class Box {
public:
Box(const T& content) : content_(content) {}
const T& getContent() const { return content_; }
void setContent(const T& content) { content_ = content; }
private:
T content_;
};
使用类模板时,我们需要显式指定模板参数:
cpp复制Box<int> intBox(42);
Box<std::string> stringBox("Hello");
类模板的成员函数可以在类定义内部直接定义(隐式内联),也可以在外部定义。外部定义时需要重复模板声明:
cpp复制template <typename T>
class Box {
// ...
void showContent() const;
};
template <typename T>
void Box<T>::showContent() const {
std::cout << content_ << std::endl;
}
类模板可以有静态成员,每个模板实例化都会有自己的静态成员副本:
cpp复制template <typename T>
class Counter {
public:
Counter() { ++count_; }
~Counter() { --count_; }
static size_t getCount() { return count_; }
private:
static size_t count_;
};
// 静态成员的定义
template <typename T>
size_t Counter<T>::count_ = 0;
使用示例:
cpp复制Counter<int> c1, c2;
Counter<double> d1, d2, d3;
std::cout << Counter<int>::getCount(); // 输出2
std::cout << Counter<double>::getCount(); // 输出3
类模板可以声明友元函数和嵌套类型:
cpp复制template <typename T>
class Container {
public:
class Iterator {
// 迭代器实现...
};
friend bool operator==(const Container& a, const Container& b) {
// 比较实现...
}
// ...
};
除了类型参数,模板还可以接受非类型参数(如整型常量、指针等):
cpp复制template <typename T, size_t N>
class Array {
public:
size_t size() const { return N; }
T& operator[](size_t index) { return data_[index]; }
const T& operator[](size_t index) const { return data_[index]; }
private:
T data_[N];
};
使用示例:
cpp复制Array<int, 10> intArray;
Array<double, 100> doubleArray;
类模板可以为模板参数提供默认值:
cpp复制template <typename T = int, size_t N = 10>
class Buffer {
// ...
};
使用示例:
cpp复制Buffer<> buffer1; // Buffer<int, 10>
Buffer<double> buffer2; // Buffer<double, 10>
Buffer<char, 20> buffer3;
模板参数本身也可以是模板,这种特性称为模板模板参数:
cpp复制template <template <typename> class Container, typename T>
class Adapter {
public:
void add(const T& value) { container_.insert(value); }
private:
Container<T> container_;
};
使用示例:
cpp复制Adapter<std::vector, int> vectorAdapter;
Adapter<std::list, std::string> listAdapter;
当代码中引用模板特化时,编译器会自动生成相应的实例化代码:
cpp复制std::vector<int> intVector; // 隐式实例化vector<int>
我们可以显式要求编译器实例化特定版本的模板:
cpp复制template class std::vector<std::string>; // 显式实例化
对于特定类型,我们可以提供完全不同的实现:
cpp复制template <>
class Vector<bool> {
// 针对bool类型的特殊实现(位压缩)
};
类模板支持部分特化,即为部分模板参数提供特定实现:
cpp复制template <typename T>
class PointerTraits<T*> {
// 针对指针类型的特殊实现
};
类型萃取(Type Traits)是模板元编程的重要技术,用于在编译时获取和操作类型信息:
cpp复制template <typename T>
struct is_pointer {
static const bool value = false;
};
template <typename T>
struct is_pointer<T*> {
static const bool value = true;
};
Substitution Failure Is Not An Error(替换失败不是错误)技术用于在模板重载解析中排除不合适的候选:
cpp复制template <typename T>
typename std::enable_if<std::is_integral<T>::value, T>::type
process(T value) {
// 仅适用于整数类型
}
C++11引入了可变参数模板,允许模板接受任意数量的参数:
cpp复制template <typename... Args>
void printAll(Args... args) {
(std::cout << ... << args) << std::endl; // C++17折叠表达式
}
模板实现通常需要放在头文件中,否则可能导致链接错误。这是因为模板需要在编译时实例化,而不是链接时。
过度使用模板可能导致代码膨胀(生成大量相似的机器代码)。可以通过以下方式缓解:
模板可能显著增加编译时间。改善方法包括:
模板相关的错误信息往往冗长难懂。可以:
在实际工程中,模板技术广泛应用于标准模板库(STL)、智能指针、元编程等领域。掌握模板编程可以显著提高代码的灵活性和复用性,但同时也需要注意避免过度复杂化设计。