//参考 《C++必知必会》 条款45 - 60
///
//内容介绍
//1.定义一个普通的模板
//2.模板显式特化
//3.模板局部特化
//4.类模板成员特化
//5.成员模板
//6.模板的模板参数
//7.泛型编程1 嵌入类型信息
//8.泛型编程2 traits
//9.泛型编程3 STL泛型编程惯例
//10.针对类型信息的特化
//11.policy
//12.模板实参推导
//13.测试代码
///
///
//1.定义一个普通的模板
///
template <typename T>
class Heap
{
public:
void push (const T &value)
{
std::cout << "Heap<T>::push" << endl;
}
T pop ();
bool empty () const
{
return h_.empty ();
}
private:
std::vector <T> h_;
};
///
//2.模板显式特化
///
//针对 const char* 专门写一个显式特化版本
template<>
class Heap <const char*>
{
public:
void push (const char *pVal) //这里不一定与主模板精确匹配
{
std::cout << "Heap<const char*>::push" << endl;
}
const char* pop ();
bool empty () const
{
return h_.empty ();
}
private:
std::vector <const char*> h_;
};
//另一个显式特化
template<>
class Heap <char*>
{
public:
void push (char *pVal)
{
std::cout << "Heap<char*>::push" << endl;
}
char* pop ();
size_t size () const;
void capitalize ();
//未提供 empty();
private:
std::vector <char*> h_;
};
//可见,主模板和显式特化之间对接口没有任何强制要求。
//不过显式特化最好接口和主模板一致,这样能使泛型函数能使用
///
//3.模板局部特化
///
template<typename T>
class Heap <const T*> //这里部分特化: T ==> const T*
{
public:
void push (T *pVal)
{
std::cout << "Heap<T*>::push" << endl;
}
//......
};
//他与显式特化区别:参数未完全确定,仍是一个模板
///
//4.类模板成员特化
///
//主模板:
template<typename T>
class Heap2
{
public:
void push (const T &value)
{
std::cout << "Heap2<T>::push" << endl;
}
T pop ();
bool empty () const { return h_.empty (); }
private:
std::vector <T> h_;
};
//如果需要针对 const char* 写显式特化,则所有接口都得重写一遍
//为此可只针对部分成员函数进行特化
template<>
void Heap2<const char *>::push (const char *const &pVal) //这里要求接口精确匹配
{
std::cout << "Heap2<const char *>::push" << endl;
}
///
//总结1:对前面的各种模板,匹配原则是优先匹配最精确的。
///
//例如测试:
void test_template1 ()
{
int x = 10;
char s[] = "abc";
Heap<int> h1;
Heap<int*> h2;
Heap<const int*> h3;
Heap<char*> h4;
Heap<const char*> h5;
h1.push (x);
h2.push (&x);
h3.push (&x);
h4.push (s);
h5.push (s);
Heap2<int> h21;
Heap2<int*> h22;
Heap2<const int*> h23;
Heap2<char*> h24;
Heap2<const char*> h25;
h21.push (x);
h22.push (&x);
h23.push (&x);
h24.push (s);
h25.push (s);
}
///
//5.成员模板
///
template <typename T>
class SList
{
public:
template <typename In> void Insert (In begin, In end) {} //这是一个成员模板
};
void SList_Test ()
{
SList<double> list1;
int datas[] = {1, 2, 3, 4};
list1.Insert (datas, datas + sizeof (datas) / sizeof (datas[0]));
}
//注: 任何非虚函数都可以写出模板
///
//6.模板的模板参数
///
//假设需要实现一个 Stack 模板,这里默认用了 deque 作为底层容器
template <typename T>
class Stack
{
public:
//...
private:
std::deque <T> s_;
};
//现在希望能够灵活点,让用户可以配置底层容器
template <typename T, class Cont>
class Stack1
{
public:
//...
private:
Cont s_;
};
//Stack1 会有个小问题了
//例如可能会被这样使用
//Stack <double, std::list<int>> stack1;
//这里的 double 和 list 内部的 int 不匹配了,编译能通过,但可能会引起 bug
//为了安全,这样实现
template <typename T, template <typename, class> class Cont = std::list> //这里有模板参数了
class Stack2
{
public:
//...
private:
Cont<T, std::allocator<T>> s_;
};
//好了,不用担心不匹配了
void Stack_test ()
{
Stack1 <double, std::list<int>> stack1; //不安全
Stack2 <double> stack2; //OK
Stack2 <int, std::vector> stack3; //OK
}
///
//7.泛型编程1 嵌入类型信息
//使用 typename 消除歧义,指示一个 类型名
//使用 template 消除歧义,指示一个 模板名
///
//泛型函数,其中typename 指示后面的 ElemT 是一个类型名
//这里的 ElemT 就是一个嵌入类型信息
template <class Cont>
void myfill (Cont &c, typename Cont::ElemT a[], int len)
{
for (int i = 0; i < len; ++i)
{
c.insert (a[i]);
}
}
//这样 myfill 支持任意类型的 Cont,只要它内部定义了 ElemT 和 insert。
//例如
template <typename T>
class MyList
{
public:
//...
typedef T ElemT; //嵌入类型信息
void insert (const ElemT&) {}
//...
};
//我们就可以这样使用它
void fill_test ()
{
MyList<int> list;
int datas[] = {1, 2, 3, 4};
myfill (list, datas, sizeof(datas) / sizeof(datas[0]));
}
//使用 template 消除歧义
//我们可能参考 STL 定义一个内存配置器
template <class T>
class MyAlloc
{
public:
//...
template <class Other>
class rebind
{
public:
typedef MyAlloc<Other> other;
};
};
//这样当我们设计一个容器时,可以传入配置器
//在这里 A 时配置 T 的,但实际在 MyList2 中还需要配置 _Node
//所以我们需要得到 A 的配置 _Node 的配置器
template <typename T, class A = MyAlloc<T>>
class MyList2
{
struct _Node
{
_Node *next;
T data;
};
//这里 template 提示后面的 rebind 是一个模板
//typename 提示最终的 other 是一个类型
typedef typename A::template rebind<_Node>::other NodeAlloc;
};
///
//8.泛型编程2 traits
///
//根据前面知识,实现一个泛型算法
template <class Cont>
typename Cont::ElemT Sum (const Cont &c, int size)
{
typename Cont::ElemT ret = typename Cont::ElemT();
for (int i=0; i<size; ++i)
{
ret += c[i];
}
return ret;
}
//这样任意类型的 Cont,只要它内部定义了 ElemT 和 [] 就可以使用 Sum。
//但如何让 Sum 应用更广泛,例如支持没有定义 ElemT 和 [] 容易,甚至支持类似容器的东西
//可以通过 traits 类实现
template <typename Cont>
struct ContTraits //支持一般容器
{
typedef typename Cont::ElemT ElemT;
};
template <int N>
struct ContTraits <int[N]> //支持 int[]
{
typedef int ElemT;
};
template <class T>
struct ContTraits <std::vector<T>> //支持 std::vector
{
typedef typename std::vector<T>::value_type ElemT;
};
//当然需要修改 Sum 了
template <class Cont>
typename ContTraits<Cont>::ElemT Sum2 (const Cont &c, int size)
{
typename ContTraits<Cont>::ElemT ret = typename ContTraits<Cont>::ElemT();
for (int i=0; i<size; ++i)
{
ret += c[i];
}
return ret;
}
//测试一下
void sum2_test ()
{
const int size = 4;
int datas[size] = {1, 2, 3, 4};
vector<int> v (datas, datas + size);
int ret1 = Sum2 (datas, size);
int ret2 = Sum2 (v, size);
}
///
//9.泛型编程3 STL泛型编程惯例
//接口使用一对迭代器
//使用 swap 进行交换
//对于比较,除了默认的 < 操作符,还可以提供一个 STL函数对象
//...
///
//上面的 Sum2 可以修改为
template <class InIt, class OutElemT>
OutElemT Sum3 (InIt _First, InIt _Last)
{
OutElemT ret = OutElemT();
for (; _First < _Last; ++_First)
{
ret += *_First;
}
return ret;
}
void sum3_test ()
{
const int size = 4;
int datas[size] = {1, 2, 3, 4};
vector<int> v (datas, datas + size);
int ret1 = Sum3<int*, int> (datas, datas + size);
int ret2 = Sum3<vector<int>::iterator, int> (v.begin (), v.end ());
}
///
//10.针对类型信息的特化
//从一个特化版本中推导出类型属性
///
//判断一个类型是否是指针
struct ISPTR_YES {};
struct ISPTR_NO {};
template <typename T>
struct IsPtr {
enum { result = false };
typedef ISPTR_NO Result;
};
template <typename T>
struct IsPtr <T *> {
enum { result = true };
typedef ISPTR_YES Result;
};
template <typename T>
struct IsPtr <T * const> {
enum { result = true };
typedef ISPTR_YES Result;
};
//针对这些信息,可以实现一个 clear_vector 函数
//当 std::vector 内部元素为指针时, clear_vector 会自动将内部的指针元素清空
template <typename T>
void _init_clear_vector_data (std::vector <T> &v, ISPTR_YES)
{
for (std::vector <T>::iterator it = v.begin (); it != v.end (); ++it)
{
delete *it;
*it = NULL;
}
}
template <typename T>
void _init_clear_vector_data (std::vector <T> &v, ISPTR_NO)
{
}
template <typename T>
void clear_vector (std::vector <T> &v)
{
_init_clear_vector_data (v, IsPtr<T>::Result ());
v.clear ();
}
//测试
void clear_vector_test ()
{
std::vector<int> v1;
v1.push_back (1);
v1.push_back (2);
v1.push_back (3);
clear_vector (v1);
std::vector<int*> v2;
v2.push_back (new int (1));
v2.push_back (new int (2));
v2.push_back (new int (3));
clear_vector (v2);
}
///
//11.policy
///
//在10中,我们简单判断 std::vector 中元素为指针的话就 delete 掉
//但这样不够灵活,比如有时指针指向数组,或有时也不期望删除指针
//总之,我们期望能对删除操作进行配置,执行不同的 delete policy (删除策略)
template <typename T, template <typename> class DeletionPolicy>
void clear_vector2 (std::vector <T> &v)
{
for (std::vector <T>::iterator it = v.begin (); it != v.end (); ++it)
{
DeletionPolicy<T> ()(*it);
}
v.clear ();
}
//然后可以定义不同的删除策略
template <typename T>
struct NoDeletePolicy {
void operator () (T ptr) {
}
};
template <typename T>
struct PtrDeletePolicy {
void operator () (T ptr) {
delete ptr;
}
};
template <typename T>
struct ArrayDeletePolicy {
void operator () (T ptr) {
delete[] ptr;
}
};
//定义无 policy 时的行为
template <typename T>
void clear_vector2 (std::vector <T> &v)
{
v.clear ();
}
//现在可以这样使用
//测试
void clear_vector_test2 ()
{
std::vector<int> v1;
v1.push_back (1);
v1.push_back (2);
v1.push_back (3);
clear_vector2<int> (v1);
std::vector<int*> v2;
v2.push_back (new int (1));
v2.push_back (new int (2));
v2.push_back (new int (3));
clear_vector2<int*, PtrDeletePolicy> (v2);
}
///
//12.模板实参推导
///
//假设有个模板函数
template <typename T>
T my_min (const T &a, const T &b)
{
return a < b ? a : b;
}
//可以这样用它
//int a = my_min (11, 12);
//不用特意指明类型,编译器会自动推导
//但这样就不行了: double d = my_min (11, 12.0);
//因为编译器无法确定类型是 int 还是 double
//必须这样用 double d = my_min<double> (11, 12.0);
//如果编译器可以自动推导出参数,则模板实参列表中靠后的模板实参可以省略
template <int n, typename T>
void repeat1 (const T &msg)
{
for (int i=0; i<n; ++i)
std::cout << msg << std::endl;
}
//可以这样用它 repeat1<10> ("abc");
//但如果 repeat 这样实现,那就麻烦了
template <typename T, int n>
void repeat2 (const T &msg)
{
for (int i=0; i<n; ++i)
std::cout << msg << std::endl;
}
//现在必须这样用了 repeat2<char*, 10> ("abc");
void template_test_12 ()
{
int a = my_min (11, 12);
double d = my_min<double> (11, 12.0);
repeat1<10> ("abc");
repeat2<const char*, 10> ("abc");
}
///
//13.测试代码
///
void template_test_all ()
{
test_template1 ();
Stack_test ();
fill_test ();
MyList2<int> list2;
sum2_test ();
clear_vector_test ();
clear_vector_test2 ();
template_test_12 ();
}
C++模板知识总结
最新推荐文章于 2024-03-08 16:09:16 发布