C++模板是泛型编程的核心工具,它允许我们编写与数据类型无关的代码。想象一下,如果你需要为int、double、char等不同类型都实现一个abs绝对值函数,传统方式需要重载多个版本,而模板只需一个通用实现。
cpp复制template<typename T>
T abs(T x) {
return x < 0 ? -x : x;
}
这个简单的abs模板函数包含几个关键要素:
template<typename T>:声明模板参数列表,T是类型参数占位符注意:模板代码通常需要放在头文件中,因为编译器需要看到完整定义才能实例化。
当编译器遇到abs(n)和abs(d)调用时,会进行隐式实例化:
abs<int>和abs<double>)实例化过程可以用这个表格表示:
| 调用表达式 | 推导类型 | 生成函数 |
|---|---|---|
| abs(n) | int | int abs(int) |
| abs(d) | double | double abs(double) |
C++模板的类型推导有一套完整规则:
T abs(T x)),则根据实参类型推导abs<int>(5.5)显式指定类型,这时会发生隐式类型转换类模板将泛型思想扩展到自定义类型,让我们看看Store这个模板类的实现。
cpp复制template <class T>
class Store {
private:
T item;
bool haveValue;
public:
Store();
T& getElem();
void putElem(const T& x);
};
关键设计考虑:
template <class T>声明类模板(class和typename在此等价)类模板的成员函数在外部定义时需要特殊语法:
cpp复制template <class T>
Store<T>::Store():haveValue(false){}
template <class T>
T& Store<T>::getElem() {
if (!haveValue) {
cout << "No item present!" << endl;
exit(1);
}
return item;
}
每个成员函数都需要:
template <class T>Store<T>::对于某些特定类型,我们可能需要特殊实现。比如针对Student类型的输出:
cpp复制// 主模板
template <class T>
class Store { /*...*/ };
// Student类型的特化
template <>
class Store<Student> {
// 特殊实现...
};
特化版本可以:
群体数据(如数组)是模板的典型应用场景,下面分析outputArray函数模板。
cpp复制template<class T>
void outputArray(const T* array, int count) {
for (int i = 0; i < count; i++) {
cout << array[i] << " ";
}
cout << endl;
}
这个模板展示了:
const T*接受任意类型数组operator<<的重载,因此T类型必须支持流输出虽然模板提供了灵活性,但仍需注意:
static_assert添加编译期检查改进版本可能包含:
cpp复制template<class T, size_t N>
void safeOutputArray(const T (&array)[N]) {
for (size_t i = 0; i < N; i++) {
cout << array[i] << " ";
}
cout << endl;
}
这种引用传递方式可以自动推导数组大小。
模板不仅用于泛型,还能进行编译期计算。比如编译期阶乘:
cpp复制template<int N>
struct Factorial {
static const int value = N * Factorial<N-1>::value;
};
template<>
struct Factorial<0> {
static const int value = 1;
};
// 使用:Factorial<5>::value
链接错误:模板实现放在cpp文件中
类型不支持操作:
cpp复制struct Foo {};
Store<Foo> s; // 如果Foo不支持比较,某些操作会失败
代码膨胀:
C++20引入了concepts来改进模板:
cpp复制template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;
template<Arithmetic T>
T abs(T x) { return x < 0 ? -x : x; }
这样abs就只能用于算术类型,编译错误更友好。
模板可以用来实现设计模式。比如策略模式:
cpp复制template<typename Strategy>
class Context {
Strategy strategy;
public:
void execute() { strategy.doAlgorithm(); }
};
struct FastStrategy {
void doAlgorithm() { /*...*/ }
};
Context<FastStrategy> context;
cpp复制template<typename... Ts>
struct TypeList {};
template<typename List>
struct Front;
template<typename Head, typename... Tail>
struct Front<TypeList<Head, Tail...>> {
using type = Head;
};
这种技术在库开发中非常有用。
每次模板实例化都会:
优化策略:
模板函数通常会被内联,这带来:
可以使用__attribute__((noinline))或#pragma控制内联。
cpp复制template<typename... Args>
void printAll(Args... args) {
(cout << ... << args) << endl; // 折叠表达式(C++17)
}
这种技术被广泛应用于标准库中。
cpp复制template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {
return t + u;
}
C++14后可以简化为:
cpp复制template<typename T, typename U>
auto add(T t, U u) {
return t + u;
}
模板错误信息通常冗长,关键技巧:
调试时打印类型信息:
cpp复制template<typename T>
void printType() {
cout << __PRETTY_FUNCTION__ << endl;
}
调用printType<int>()会输出包含类型信息的字符串。
cpp复制template<
class T,
class Allocator = std::allocator<T>
> class vector;
vector的模板参数:
标准库算法大多是模板:
cpp复制template< class InputIt, class UnaryPredicate >
bool all_of(InputIt first, InputIt last, UnaryPredicate p);
这种设计使得算法可以应用于:
cpp复制using IntStore = Store<int>;
模板是C++最强大的特性之一,但也需要谨慎使用。我建议从简单模板开始,逐步掌握更高级的技巧。在实际项目中,模板最常见的用途是实现类型安全的容器和算法,这也是标准库的设计哲学。