栈类模板的构造与使用
#include <vector>
#include <cassert>
#include <iostream>
template<typename T>
class Stack {
private:
std::vector<T> elems;
public:
void push(const T& elem);
T pop(); // 这里实现带返回值的pop(并不保证异常安全)
const T& top() const;
bool empty() const {
return elems.empty();
}
Stack() = default;
Stack(const Stack&);
Stack& operator=(const Stack&);
};
template<typename T>
bool operator==(const Stack<T>& lhs, const Stack<T>& rhs);
template<typename T>
void Stack<T>::push(const T& elem) {
elems.push_back(elem);
}
template<typename T>
T Stack<T>::pop() {
assert(!elems.empty());
T elem = elems.back();
elems.pop_back();
return elem;
}
template<typename T>
const T& Stack<T>::top() const {
assert(!elems.empty());
return elems.back();
}
int main() {
Stack<int> t1;
Stack<std::string> t2;
t1.push(1); // 对 Stack<int>::push() 做实例化
t1.push(2);
t2.push("hello"); // 对 Stack<std::string>::push() 做实例化
std::cout<<t2.top()<<std::endl; // 对 Stack<std::string>::top() 做实例化
int tmp = t1.pop(); // 对 Stack<int>::pop() 做实例化
std::cout<<tmp<<std::endl;
// 其他类内函数,均没有被实例化
return 0;
}
注意,编译器只会对会调用的函数进行实例化,对于类模板,只有在使用类内函数时,才会进行实例化,节省时间与空间。(如果 类模板具有静态成员,则对于使用类模板的每个类型实例,这些成员也会实例化一次。)
概念concept
一个模板类的实例化,不可避免地会调用一些必要函数(如构造函数等),然而,这些必要操作可能并没有被实现,比如:
#include <iostream>
#include <utility>
#include <vector>
template<typename T>
class A {
public:
std::vector<T> arr;
A() = default;
void push(const T& data) {
arr.push_back(data);
}
void printAll() const {
for( const T& it : arr ) {
std::cout<<it<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
}
}
};
int main() {
// 因为int有默认的<<操作符,所以可以正常输出
A<int> a;
for(int i = 0; i < 5; i++) {
a.push(i);
}
a.printAll();
A<std::pair<int,int>> b;
for(int i = 0; i < 5; i++) {
b.push(std::make_pair(i,i));
}
b.printAll(); // 失败,因为pair 没有默认的<<操作符
return 0;
}
这种情况,推荐使用C++20 中的concept,在编译阶段检查模板参数类型是否满足特定的属性或要求,增强了模板编程的灵活性和类型安全性。
类模板特化
需要为每个模板函数都实现一份特化版本:
#include <iostream>
#include <utility>
#include <vector>
template<typename T>
class A {
public:
std::vector<T> arr;
A() = default;
void push(const T& data) {
arr.push_back(data);
}
void printAll() const {
for( const T& it : arr ) {
std::cout<<it<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
}
}
};
template<>
class A<std::pair<int, int>> {
public:
std::vector<std::pair<int, int>> arr;
A() = default;
void push(const std::pair<int, int>& data) {
arr.push_back(data);
}
void printAll() const {
for( const std::pair<int,int>& it : arr ) {
std::cout<<it.first<<":"<<it.second<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
}
}
};
int main() {
// 因为int有默认的<<操作符,所以可以正常输出
A<int> a;
for(int i = 0; i < 5; i++) {
a.push(i);
}
a.printAll();
A<std::pair<int,int>> b;
for(int i = 0; i < 5; i++) {
b.push(std::make_pair(i,2*i));
}
b.printAll(); // 调用的是特化模板,所以成功
return 0;
}
注意,特化实现可以和主模板完全不同,比如,特化版的容器,完全可以不用vector,而用deque。
偏特化
特化(Full Specialization):完全替代原有的模板定义,适用于特定的模板参数组合。
偏特化(Partial Specialization):只替代模板定义的部分内容,适用于一类特定但不完全确定的模板参数组合。
上一节是对 pair<int,int> 的模板类特化,下面这个是对pair<T,T>的偏特化
#include <iostream>
#include <utility>
#include <vector>
template<typename T>
class A {
public:
std::vector<T> arr;
A() = default;
void push(const T& data) {
arr.push_back(data);
}
void printAll() const {
for( const T& it : arr ) {
std::cout<<it<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
}
}
};
template<typename T>
class A<std::pair<T, T>> {
public:
std::vector<std::pair<T, T>> arr;
void push(const std::pair<T, T>& data) {
arr.push_back(data);
}
void printAll() const {
for( const std::pair<T,T>& it : arr ) {
std::cout<<it.first<<":"<<it.second<<std::endl; // 这里默认需要类型T对<<操作符有对应的重载
}
}
};
int main() {
// std::pair<int,int> 版本
A<std::pair<int,int>> a;
for(int i = 0; i < 5; i++) {
a.push(std::pair<int,int>(i,i));
}
a.printAll();
// std::pair<double,double> 版本
A<std::pair<double,double>> b;
for(int i = 0; i < 5; i++) {
b.push(std::pair<double,double>(i*0.2,i*0.3));
}
b.printAll();
return 0;
}
类型别名
可以通过typedef 或者 using(C++11) 来简化类模板的使用:
typedef std::pair<int,int> PAIR_INT;
using PAIR_DOUBLE = std::pair<double,double>;
A<PAIR_INT> a;
for(int i = 0; i < 5; i++) {
a.push(std::pair<int,int>(i,i));
}
a.printAll();
A<PAIR_DOUBLE> b;
for(int i = 0; i < 5; i++) {
b.push(std::pair<double,double>(i*0.2,i*0.3));
}
b.printAll();
模板聚合
也就是模板和特定类型混用,聚合类 (不由用户提供、显式或继承的构造函数的类/结构,没有 private 或 protected 的非静态 数据成员,没有虚函数,也没有 virtual、private 或 protected 基类) 也可以是模板。:
template<typename T> struct ValueWithComment {
T value;
std::string comment; };
// C++17以后可以定义推导策略
ValueWithComment(char const*, char const*) -> ValueWithComment<std::string>;
ValueWithComment vc2 = {"hello", "initial value"};
总结
• 类模板是在实现时保留一个或多个类型参数的类。
• 要使用类模板,需要将类型作为模板参数传递。并为这些类型,实例化 (并编译) 类模板。
• 对于类模板,只有调用的成员函数会实例化。
• 可以为某些类型特化类模板。
• 可以偏特化某些类型的类模板。
• C++17后,可以从构造函数中自动推导出类模板的参数。
• 可以定义聚合类模板。
• 若声明为按值调用,则模板类型的调用参数会衰变。
• 模板只能在全局/命名空间作用域或类声明内部声明和定义。