C++相关语法基础
模板特化(template specialization)
通用模板对于某种特定的类型可能是错误的,所以可能需要对特定的类型单独定义模板的实现。另一方面,可以利用特定类型的特点进行优化。
模板特化是指这样一个定义,该定义指定一个或多个模板的形参实际类型或值。 形式如下:
- template<>
- 模板名(可以是函数或类)加一对尖括号,尖括号中指定模板形参的实际类型。
- 函数形参表和函数体或类定义。
示例:
|
类模板的部分特化(partial specialization)
部分特化是指针对template参数更进一步的条件限制所涉及出来的一个特化版本。 这里的更进一步的条件限制并不仅限于指定模板形参的实际类型。 编译器在实例化时,优先使用特化的版本。
示例:
|
迭代器关联类型
type_value
在使用容器时,我们经常会用到容器内部元素的类型,C++本身并不能获取变量类型。大多数算法的参数都是一些迭代器的范围,算法本身并不知道迭代器所指对象的内容。即使通过
来获取,得到的也只能是类型名称,不能用来定义变量。一个简单的解决方法是:迭代器的内部定义一个内部类型。
|
STL容器都会定义
作为迭代器所指对象的类型。我们可以通过 来提取这个类型。
|
注意上面的typename关键字,因为I是一个模板参数,在它被实例化之前,编译器并不知道value_type是个啥。所以必须显式地告诉编译器这是一个类型。
当我们需要知道迭代器所指对象的类型时,只需要用
即可。但是原生的指针也是一种迭代器,它并不是一个类,我们也不能在其内部定义一个类型。 这个时候我们就需要利用C++的模板偏特化特性了。iterator_traits还有一个偏特化的版本,这个版本用来处理原生指针。
这里虽然没有直接指定特化的类型,但是进一步对类型进行了约束,即规定实参类型必须是指针,所以也是一种偏特化。
这样,我们可以使用 得到int类型。
除此之外,还有一个版本用来处理常指针,如 ,使得得到的结果是int,而不是const int。
|
第一部分 为什么要有萃取技术
既然是一种智能指针,iterator也要对一个原生指针进行封装,而问题就源于此,当我们需要这个原生指针所指对象的类型的时候(例如声明变量),怎么办呢?
Case1 对于函数的局部变量
这种情况我们可以采用模版的参数推导,例如:
template <class T> void func(T iter)
如果T是某个指向特定对象的指针,那么在func中需要指针所指向对象类型的变量的时候,怎么办呢?这个还比较容易,模板的参数推导机制可以完成任务,如下:
template <class T, class U>
void func_impl(T t, U u) {
U temp; // OK, we’ve got the type
// The rest work of func…
}
template <class T>
void func(T t) {
func_impl(t, *t); // forward the task to func_impl
}
通过模板的推导机制,我们轻而易举的或得了指针所指向的对象的类型,但是事情往往不那么简单。例如,如果我想把传递给func的这个指针参数所指的类型作为返回值,显然这个方法不能凑效了,这就是我们的case 2。
Case2 对于函数的返回值
尽管在func_impl中我们可以把U作为函数的返回值,但是问题是用户需要调用的是func,于是,你不可能写出下面的代码:
template <class T, class U>
U func_impl(T t, U u) {
U temp; // OK, we’ve got the type
// The rest work of func…
}
template <class T>
(*T) func(T t) { // !!!Wrong code
return func_impl(t, *t); // forward the task to func_impl
}
int i =10;
cout<<func(&i)<<endl; // !!! Can’t pass compile
红色的部分概念上如此正确,不过所有的编译器都会让你失望。这个问题解决起来也不难,只要做一个iterator,然后在定义的时候为其指向的对象类型制定一个别名,就好了,像下面这样:
template <class T>
struct MyIter {
typedef T value_type; // A nested type declaration, important!!!
T* ptr;
MyIter(T* p = 0) : ptr(p) {}
T& operator*() const { return *ptr; }
};
而后只要需要其指向的对象的类型,只要直接引用就好了,例如:
template <class I>
typename I::value_type func(I iter) { return *iter; }
很漂亮的解决方案,看上去一切都很完美。但是,实际上还是有问题,因为func如果是一个泛型算法,那么它也绝对要接受一个原生指针作为迭代器,但是显然,你无法让下面的代码编译通过:
int *p = new int(52);
cout<<func(p)<<endl; // !!!Is there a int::value_type?? Wrong Code here
我们的func无法支持原生指针,这显然是不能接受的。此时,template partial specialization就派上了用场。
Solution :template partial specialization是救世主
既然刚才的设计方案仍不完美,那我们就再加一个间接层,把智能指针和原生指针统统的封装起来。在讨论之前,先要澄清一下template partial specialization的含义。所谓的partial specialization和模板的默认参数是完全不同的两件事情,前者指的是当参数为某一类特定类型的时候,采用特殊的设计,也就是说是“针对template参数更进一步的条件限制所设计出来的一个特化版本”;而默认参数则是当不提供某些参数的时候,使用的一个缺省。
参考:partial specialization的语法
Template <typename T> class C<T*> {} // 为所有类型为T*的参数而准备的特殊版本
好了,下面我们就找一个专职的负责人,用来封装迭代器封装的对象类型。首先,把我们刚才的MyIter重新包装一下:
template <class I>
struct iterator_traits {
Typedef I::value_type value_type;
}
现在,我们的func又有了新的面貌:
template <class I>
typename iterator_traits<I>::value_type func(I ite) {
return *ite;
}
尽管这次我们的函数返回值的长度有些吓人,但是,我们的确为原生指针找到了好的解决方案。只要为原生指针提供一个偏特化的iterator_traits就OK了。如下:
template <class I>
struct iterator_traits<T*> {
typedef T value_type;
};
下面,我们终于可以让func同时支持智能指针和原生指针了:
template <class I>
struct iterator_traits {
Typedef I::value_type value_type;
}
template <class T>
struct iterator_traits<T*> {
typedef T value_type;
};
template <class I>
typename iterator_traits<I>::value_type func(I ite) {
return *ite;
}
int main() {
MyIter<int> iter = new int(520);
int *p = new int(520);
// This time the following two statements will success
cout<<func(iter)<<endl;
cout<<func(p)<<endl;
return 0;
}
但是,我们离最后的成功还有最后一步,如果,我们需要声明一个value_type类型的左值,但是却给iterator_traits传递了一个const int*,显然结果有问题,于是,为const T*也另起炉灶,准备一份小炒:
template<class T>
struct iterator_traits<const T*> {
typedef T value_type;
}
OK ,现在万事大吉,无论是正宗迭代器,原生指针,const原生指针,我们都可以利用iterator_traits萃取出其封装的对象的类型,萃取技术由此而来。