最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
声明 template 类型参数,class 和 typename 的意义完全相同,如:
template<class T> class Widget; //使用 class
template<typename T> class Widget; //使用typename
然而,C++并不总是把 class 和 typename 视为等价。有时候你一定得使用 typename 。为了了解使用的时机,我们来说一下在 template 内指涉的两种名称。
假设,我们有个模板函数,接受一个 STL 兼容容器为参数,容器内持有的对象可被赋值为 int,它的功能是打印第二元素值,它通过不了编译(原因后面会讲):
template<typename C>
void print2nd(const C& container) //注意:它不能通过编译
{
if(container.size() >= 2) {
C::const_iterator iter(container.begin());
++iter;
int value = *iter;
std::cout<<value;
}
}
我们来谈一下上述函数中的两个局部变量 iter 和 value:
-
iter 的类型是
C::const_iterator
,实际的类型取决于 template参数 C。template 内出现的名称如果相依于某个 template参数,称之为从属名称 (dependent name)(如 template参数 C),如果从属名称在 class 内呈嵌套状,我们称它为嵌套从属名称(nested dependent name),而C::const_iterator
就是这样的一个名称。 -
value 是int类型,并不依赖任何 template参数,这叫非从属名称(non-dependent name)。
嵌套从属名称 可能会导致 解析困难,举个例子:
template<typename C>
void print2nd(const C& container)
{
C::const_iterator* x;
...
}
看起来好像我们声明 x 为一个局部变量,它是个指针,指向一个 C::const_iterator
,编译器会这样认为吗?
如果 C::const_iterator
不是个类型,且有个 static 成员变量恰巧被命名为 const_iterator
,或如果 x 恰巧是个 全局变量呢?
那样的话,上述代码不再是声明一个 局部变量,而是一个相乘动作:C::const_iterator
乘以 x。
在我们知道 C 是什么之前,没有任何办法可以知道 C::const_iterator
是否为一个类型。而当编译器开始解析 template print2nd 时,尚未知道 C 是什么东西。当然,C++有规则可以解析这个歧义状态:如果解析器在 template 中遭遇一个嵌套从属名称,它便假设这名称不是个 类型,除非你告诉它是。所以,默认情况下嵌套从属名称不是类型。
template<typename C>
void print2nd(const C& container) //不能通过编译
{
if(container.size() >= 2) {
C::const_iterator iter(container.begin()); //这个名称被假设为 非类型
...
}
}
这样你就清楚第一个例子为什么不能通过编译了吧。iter 声明式只有在 C::const_iterator
是个类型时才合理。
如果我们想告诉编译器 C::const_iterator
是个类型,就必须在紧邻它的前一个位置加上关键字 typename。
template<typename C>
void print2nd(const C& container) //合法代码,能够通过编译
{
if(container.size() >= 2) {
typename C::const_iterator iter(container.begin()); //这个名称被假设为 非类型
...
}
}
所以,你想在 template 中指涉一个嵌套从属类型名称,就必须在紧邻它的前一个位置加上关键字 typename。
typename 只被用来验明嵌套从属类型名称,其他名称不该有它存在。例如下面的函数模板,接受一个容器和一个”指向该容器“的迭代器:
template<typename C> //允许使用 typename 或 class
void f(const C& container, //不允许使用 typename
typename C::const_iterator) //C::const_iterator 时嵌套从属名称,必须以 typename 为前导
typename 必须作为嵌套从属类型名称的前缀词 ,这一规则 有一个例外,typename 不可以出现在 base_class list 内的嵌套从属名称类型名称之前,也不可在 member initialization list(成员初始化列表)中作为 base class 修饰符,如:
template<typename T>
class Derived: public Base<T>::Neted { // base class list中 不允许 typename
public:
explicit Derived(int x): Base<T>::Nested(x) // 成员初始化列表中 不允许 typename
{ // 嵌套从属名称既不在 base class list,也不在成员初始化列表中,作为一个基类修饰符需加上 typename
typename Base<T>::Nested temp;
...
}
...
};
接下来再看一个典型例子。假设写一个 function template,让它接受一个迭代器,我们将为这个迭代器所指涉的对象做一份 局部l副本 temp:
template<typename IterT>
void workWithIterator(IterT iter)
{
typename std::iterator_trains<IterT>::value_type temp(*iter);
//std::iterator_trains<IterT>::value_type 是个标准的 traits class(见条款47)的一种运用
//相当于说"类型为 IterT 的对象所指之物的类型"
//这个语句意思是声明一个局部变量 temp,temp 的类型是 IterT 对象所指物的相同类型,并将 temp 初始化为 iter 所指物
//如果 IterT 是 vector<int>::iterator,temp 的类型就是 int
//如果 IterT 是 list<string>::iterator,temp 的类型就是 string
...
}
由于 std::iterator_trains<IterT>::value_type
是个嵌套从属类型名称(value_type 被嵌套于 iterator_trains<IterT>
之内,而 IterT
是个 template参数 ),所以我们必须在它前面加上 typename。
我们可以用 typedef 来为 std::iterator_trains<IterT>::value_type
取个别名,如:
template<typename IterT>
void workWithIterator(IterT iter)
{
typedef typename std::iterator_trains<IterT>::value_type value_type;
value_type temp(*iter);
...
}
Note:
- 声明 template参数 时,前缀关键字 class 和 typename 可互换
- 请使用关键字 typename 标识嵌套从属类型名称;但不得在 base class list 或 member initialization list 内以它作为base class 修饰符