C++面向对象高级开发-兼谈对象模型-模板专题
Notes
- 类模板
- 函数模板
- 成员模板
- 模板特化
- 模板偏特化-个数的偏
- 模板偏特化-范围的偏
- 模板模板参数
类模板
#include <cstdlib>
template <typename T>
class Complex {
public:
Complex(T r = 0, T i = 0) : re(r), im(i) { }
Complex& operator += (const Complex&);
T real () const { return re; }
T imag () const { return im; }
private:
T re, im;
friend Complex& __doapl(Complex*, const Complex&);
};
int main(int argc, const char * argv[]) {
Complex<double> c1(2.5, 1.5);
Complex<int> c2(2,6);
return EXIT_SUCCESS;
}
在设计一个类的时候,若存在可以由使用者任意指定数据类型的变量,上述复数类中的实部和虚部即为满足条件的变量,使用T替代原有数据类型,在类头前增加模板类声明template <typename T>
即可。在使用时指明其数据类型即可,如Complex<int> c2(2,6);
即为复数类中的T使用int替换。
函数模板
#include <cstdlib>
class stone {
public:
stone(int w, int h, int we) : _w(w), _h(h), _weight(we) { }
bool operator < (const stone& rhs) const {
return _weight < rhs._weight;
}
private:
int _w, _h, _weight;
};
template <class T>
inline const T& min(const T& a, const T& b) {
return b < a ? b : a;
}
int main(int argc, const char * argv[]) {
stone r1(2,3), r2(3,3), r3;
r3 = min(r1, r2);
return EXIT_SUCCESS;
}
当设计一个函数时,其功能与数据类型无关时,上述代码中函数inline const T& min(const T& a, const T& b);
即满足条件,可将这样的函数设计为函数模板。min函数即为取传入的两个参数中较小的一个,与数据类型无关。设数据类型为T,可在函数声明前增加template <class T>
。在使用时,编译器会对函数模板进行实参推导。语句r3=min(r1,r2);
中未指出类型T,编译器推导T的结果应为stone类型,访问stone类中尝试调用stone::operator <
。模板块编译时不能保证成功,在使用时会重新编译模板块。
成员模板
template <class T1, class T2>
struct pair {
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() : first(T1()), second(T2()) { }
pair(const T1& a, const T2& b) : first(a), second(b) { }
template <class U1, class U2>
pair(const pair<U1, U2>& p) : first(p.first), second(p.second) { }
};
代码中的pair(const pair<U1, U2>& p)
即为成员模板。
模板类中包含的成员函数亦使用模板时,可抽象为如图上半部分所示的基类与派生类。例中声明语句即为将派生模板类作为参数调用的构造函数,放入由基模板类构成的类型中。因为U1与U2为参数传入的构造函数使用初值列对当前对象中的T1与T2类型对象进行赋值,故U1与U2必为T1与T2的派生类。在标准库中大量类声明的构造函数中均存在成员模板。
#include <memory>
class Base1 {};
class Derived1 : public Base1 {};
template<typename _Tp>
class shared_ptr : public __shared_ptr<_Tp> {
...
template<typename _Tp1>
explicit shared_ptr(_Tp1* __p) : __shared_ptr<_Tp>(__p) {}
...
};
int main(int argc, const char * argv[]) {
Base1* ptr = new Derived1; //up-cast;
shared_ptr<Base1> sptr(new Derived1); //Simulate up-cast;
return 0;
}
shared_ptr智能指针中。Base1为基类,Derived1为派生类。sptr指针的声明使用派生类对象为参数,基类为智能指针基类数据类型。
模板特化
模板特化又称模板全特化,即Full-Specialization。常见的泛化模板应用一般如下:
//generalization definition
template <class Key>
struct hash {};
//generalization use
std::cout << hash<Key> << std::endl;
在模板特化中,由于类型已经绑定,故template<>中为空。可在类名后的<>中写入类型。hash<long>()
为临时对象,(1000)
为调用操作符重载函数operator()
。
// specialization definition
template<>
struct hash<char> {
size_t operator() (char x) const { return x; }
};
template<>
struct hash<int> {
size_t operator() (int x) const { return x; }
}
template <>
struct hash<long> {
size_t operator() (long x) const { return x; }
}
// specialization use
std::cout << hash<long>()(1000) << std::endl;
模板偏特化-个数的偏
模板偏特化,即Partial-Specialization,模板偏特化又可分为个数的偏和范围的偏。
template<typename T, typename Alloc=...>
class vector {
...
};
在template<>
括号中以,分隔的被称作为模板参数。当进行类设计时,若需要限定其中一个模板参数,可在template括号中撤销模板参数,在类名括号中声明限定模板参数,及其他未定模板参数。即将typename T
绑定至bool。
template<typename Alloc=...>
class vector<bool, Alloc> {
...
};
模板个数偏特化的绑定必须为从左至右依次绑定。不可跳跃绑定。
模板偏特化-范围的偏
若要对模板参数的范围限定为指针,指针可指向任意数据类型时可有:
template <typename T>
class C {
...
};
template <typename U>
class C<U*> {
...
};
C<string> obj1;
C<string*> obj2;
泛化模板使用的模板参数与特化模板中的模板参数无关。其中,obj1调用泛化模板,obj2调用偏特化模板。
模板模板参数
模板模板参数,即Template-Template Parameter.在模板参数中一个又为模板时,称为模板模板参数。
template<typename T, template <typename T> class Container >
class XCls {
private:
Container<T> c;
public:
...
};
template<typename T>
using Lst = list<T, allocator<T>>;
//XCls<string, list> mylst1;
XCls<string, Lst> mylst2;
在模板参数中,例template<typename T>
与template<class T>
均会出现,在模板参数中,typename与class互通。上例中,第二模板参数Container以第一模板参数为参数,语句XCls<string, list> mylst1;
为传入Container容器list,并传入Container容器list的数据类型为string时,需使用模板模板参数。类中定义为list<string> c;
,Container容器需要第二/第三模板参数,语法错误编译失败。解决方法为定义Lst修改第二参数类型,即增加分配器模板参数后即可通过。
template<typename T, template<typename T> class SmartPtr>
class XCls {
private:
SmartPtr<T> sp;
public:
XCls() : sp(new T) { }
};
XCls<string, shared_ptr> p1;
//XCls<double, unique_ptr> p2;
//XCls<int, weak_ptr> p3;
XCls<long, auto_ptr> p4;
此例中第二模板参数传入为SmartPtr智能指针,大部分智能指针模板类均接收一个参数。使用传入的智能指针以第一模板参数为智能指针的模板参数,注释句因unique_ptr与weak_ptr的特性不可使用。
template <class T, class Sequence = deque<T>>
class stack {
friend bool operator == <> (const stack&, const stack&);
friend bool operator < <> (const stack&, const stack&);
protected:
Sequence c;
...
};
stack<int> s1;
stack<int, list<int>> s2;
标准库类stack接收两个模板参数,以class Sequence = deque<T>
中Sequence为类型创建对象,语句stack<int> s1;
第二模板参数有默认值,可略过。而语句stack<int, list<int>> s2;
中list<int>
已绑定,无变化可能,故stack类中第二模板参数不是模板模板参数。