Featured image of post C++面向对象高级开发-兼谈对象模型-模板专题

C++面向对象高级开发-兼谈对象模型-模板专题

类模板, 函数模板, 成员模板, 模板特化, 模板偏特化-个数的偏, 模板偏特化-范围的偏, 模板模板参数

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类中第二模板参数不是模板模板参数。