本章给出模板的一些更深入的基础知识,它们都是和模板的实际应用密切相关的,包括关键字typename的另一种用法,
把成员函数和嵌套类也定义成模板,模板的模板参数(template template parameters),零初始化和使用字符串作为模板实参时所要注意的一些细节,虽然这些技术具有很强的技巧性,但每个C++程序员日常都应对它略有耳闻。
关键字typename
在C++标准化过程中,引入关键字typename是为了说明:模板内部的标识符可以是一个类型,譬如下面的例子:
template <typename T>
class MyClass {
typename T::SubType * ptr;
...
};
上面程序中,第2个typename被用来说明:SubType是定义于类T内部的一种类型。因此,ptr是一个指向T::SubType类型的指针。
如果不使用typename, SubType就会被认为是一个静态成员,那么它应该是一个具体的变量或对象,于是,下面的表达式:
T::SubType * ptr
会被看作是类T的静态成员SubType和ptr的乘积。
通常而,当某个依赖于模板参数的名称是一个类型时,就应该使用typename。(这个问题在9.3.2小节详细讨论)
让我们来考虑一个typename的典型应用,即在模板代码中访问STL容器的迭代器:
#ifndef PRINTCOLL_H
#define PRINTCOLL_H
#include <iostream>
template <typename T>
void printcoll(T const &coll)
{
typename T::const_iterator pos; //用于迭代coll的迭代器
typename T::const_iterator end(coll.end()); //结束位置
for(pos = coll.begin(); pos != end; ++pos)
{
std::cout << *pos << ' ';
}
std::cout << std::endl;
}
#endif // PRINTCOLL_H
在这个函数模板中,调用参数是一个T类型的STL容器。为了迭代容器中的所有元素,我们借助于迭代器类型;
而在STL容器类中,有声明迭代器类型const_iterator的都可以使用,比如vector, list, queue没有这个类型,所有不能使用
下面看具体的示例
#include <iostream>
#include "printcoll.h"
#include <vector>
#include <list>
#include <queue>
int main()
{
std::vector<int> v;
v.push_back(34);
v.push_back(23);
v.push_back(98);
printcoll(v);
std::list<int> li;
li.push_back(56);
li.push_back(23);
li.push_back(94);
printcoll(li);
std::queue<int> qu;
qu.push(87);
qu.push(63);
qu.push(82);
//printcoll(qu);//编译错误,const_iterator 未声明的标识符
std::cout << "Hello World!" << std::endl;
return 0;
}
.template构造(详细在9.3.3介绍)
使用this->
对于具有基类的类模板,自身使用名称x并不一定等同于this->。即使用该x是从基类继承获得的,也是如此。
例如:
template<typename T>
class Base {
public:
void exit();
};
template <typename T>
class Derived: Base<T> {
public:
void foo() {
exit();//调用外部的exit()或者出现错误
}
};
在这个例子中,在foo()内部决定要调用哪一个exit()时,并不会考虑基类Base中定义的exit()。因此,你如果不是获取一个错误,就是调用了另一个exit().
这个问题将在9.4.2小节详细讨论。
现在建议你记住一条南规则:对于那些在基类中声明,并且依赖于模板参数的符号(函数或者变量等),你应该在它们前面使用this->或者Base<T>::。如果希望完全避免不确定性,你可以(使用诸如this->和Base<T>::等)限定(模板中)所有的成员访问。