c++ 类模板的使用
1、1、举例子
除了可以申明函数模板外,也还可以声明类模板。举个例子(实现一个堆栈):
template<typename T>
class Stack {
std::vector<T> v;
public:
Stack();
Stack(const Stack<T>&); // T是同一类型的类模板才能拷贝
Stack<T>& operator=(const Stack<T>&);
void push(const T&);
void pop();
const T& top() const;
bool empty() const;
};
template<typename T>
Stack<T>::Stack()
{}
template<typename T>
Stack<T>::Stack(const Stack<T>& rhs) : v(rhs.v)
{}
template<typename T>
Stack<T>& Stack<T>::operator=(const Stack<T>& rhs)
{
v = rhs.v;
return *this;
}
template<typename T>
void Stack<T>::push(const T& x)
{
v.emplace_back(x);
}
template<typename T>
void Stack<T>::pop()
{
assert(!v.empty());
v.pop_back();
}
template<typename T>
const T& Stack<T>::top() const
{
assert(!v.empty());
return v.back();
}
template<typename T>
bool Stack<T>::empty() const
{
return v.empty();
}
2、定义好类模板后,它的调用方法:
int main()
{
using IntStack = Stack<int>; // typedef Stack<int> IntStack
IntStack intStack; // Stack<int> intStack
intStack.push(42);
std::cout << intStack.top(); // 42
Stack<std::string> stringStack;
stringStack.push("hi");
std::cout << stringStack.top(); // hi
stringStack.pop();
}
3、除了int类型外,模板实参可以是任何类型
Stack<double*> doublePtrStack;
Stack<Stack<int>> intStackStack;
4、成员函数只有被调用到时才实例化
如果类模板有static数据成员,每种实例化类型都会实例化static数据成员。static成员函数和数据成员只被同类型共享
template<typename T>
class A {
static std::size_t n;
public:
static std::size_t count();
};
template<typename T>
std::size_t A<T>::n = 0;
A<std::string> a; // 实例化A<std::string>::n
A<int> b, c, d; // 实例化A<int>::n,bcd共享A<int>::count()和A<int>::n
std::size_t n = A<int>::count(); // 实例化A<int>::count()
n = b.count(); // 使用A<int>::count()
n = A::count(); // 错误:必须指定模板参数,否则无法得知实例化版本
5、2、类模板的部分使用
由于成员函数只有被调用到时才实例化,模板实参只要提供必要的操作,而非所有需要的操作。如Stack提供一个printOn对每个元素调用operator<<,即使没有对元素定义operator<<也能使用这个类。只有调用printOn时才会产生错误,因为这时不能对这些元素实例化operator<<
template<typename T>
class Stack {
...
void printOn(std::ostream&) const;
};
template <typename T>
void Stack<T>::printOn(std::ostream& os) const
{
for (const T& x : v) os << x << ' ';
}
Stack<std::pair<int, int>> s; // std::pair没有定义operator<<
s.push({1, 2}); // OK
s.push({3, 4}); // OK
std::cout << s.top().first << s.top().second; // 34
s.printOn(std::cout); // 错误:元素类型不支持operator<<
6、与其使用printOn函数打印元素,不如重载operator<<,然而通常operator<<会实现为非成员函数。下面在类内定义友元,它是一个普通函数
template<typename T>
class Stack {
...
void printOn(std::ostream& os) const;
friend std::ostream& operator<<(std::ostream& os, const Stack<T>& stack) {
stack.printOn(os);
return os;
}
};
7、如果在类外定义友元,类模板参数不可见,事情会复杂很多
有两个解决方案,一是隐式声明一个新的函数模板,并使用不同的模板参数
template<typename T>
class Stack {
…
template<typename U>
friend std::ostream& operator<<(std::ostream&, const Stack<U>&);
};
// 类外定义template<typename U>
std::ostream& operator<<(std::ostream& os, const Stack<U>& stack)
{
stack.printOn(os);
return os;
}
8、二是将友元前置声明为模板,而友元参数中包含类模板,这样就必须先前置声明类模板
template<typename T> // operator<<中参数中要求Stack模板可见class Stack;
template<typename T>
std::ostream& operator<<(std::ostream&, const Stack<T>&);
// 随后就可以将其声明为友元template<typename T>
class Stack {
…
friend std::ostream& operator<< <T> (std::ostream&, const Stack<T>&);
};
// 类外定义template<typename T>
std::ostream& operator<<(std::ostream& os, const Stack<T>& stack)
{
stack.printOn(os);
return os;
}
9、同样,函数只有被调用到时才实例化,元素没有定义operator<<时也可以使用这个类,只有调用operator<<时才会出错
Stack<std::pair<int, int>> s; // std::pair没有定义operator<<
s.push({1, 2}); // OK
s.push({3, 4}); // OK
std::cout << s.top().first << s.top().second; // 34
std::cout << s << '\n'; // 错误:元素类型不支持operator<<