Effective C++ — 条款42:了解typename的双重意义
2017-12-02 16:42
369 查看
了解typename的双重意义
提到一个问题:以下template声明式中,class和typename有什么不同?template<class T> class Widget; //使用"class"
template<typename T> class Widget; //使用"typename"
答案是没有不同! 当我们声明template类型参数的时候,class和typename的意义完全相同. 某些程序员始终比较喜欢class,因为可以少打几个字.还
有一些大佬喜欢typename,因为它暗示参数并非一定得是个class类型.少数开发人员在接受任何类型的时使用typename,而在只接受用户自定义类型时
保留旧式的class.然而从C++的角度来看,声明template参数时,不论使用关键字class或typename,意义完全相同.
然而 C++ 并不总是把class 和 typename视为等价. 有时候你一定得使用typename.为了了解其时机,我们必须先谈谈你可以在template内指(refer to)
的两种名称. 看一下代码:
template<typename C> void prnt2nd(const C& container) //打印容器内的第二元素 { if (container.size() >= 2) { C::const_iterator iter(container.begin()); //取得第一元素的迭代器 ++iter; //将iter移往第二元素 int value = *iter; //将该元素复制到某个int std::cout << value; //打印那个int } }
我在代码当中特别强调了两个local变量iter和value. iter的类型是C::const_iterator,实际是什么必须取决于template参数C,tempalte内出现的
名称如果依赖于某个template参数,称之为从属名称. 如果从属名称在class内呈嵌套状,我们称它为嵌套从属名词. C::const_iterator 就是这样的
一个名称. 实际上他还是一个嵌套从属类型名称,也就是个嵌套从属名称并且指涉某类型.
print2nd内的另一个local变量value,其类型是int。int是个并不倚赖任何template参数的名称.这样的名称称之为非从属名称. 叫它独立名称也可以.
我们上面讲到的嵌套从属名称有可能导致解析困难.举个例子,假设我们令print2nd更愚蠢一点,这样起头:
template<typename C> void print2nd(const C& container) { C::const_iterator* x; ... }
看起来好像我们声明了一个x为一个local变量,它是一个指针,指向一个C::const_iterator. 但是我们之所以这样认为是因为我们"已经知道"啦
C::const_iterator是一个类型.如果C::const_iterator不是一个类型呢? 如果有一个static成员变量碰巧命名为const_iterator,如果x碰巧
是一个global变量名称呢? 那样的话上述代码就不再是声明一个local变量,而是一个相乘动作. 虽然这在我们平时是看作不可能的,不过呢! C++
解析器可不能容许偶然,他必须考虑到所有情况。
在我们知道C是什么之前,没有任何办法可以知道C::const_iterator是否为一个类型.而当编译器开始解析template print2nd时,尚未知道C是一
个什么东西. C++有个规则可以解析此一歧义状态:如果解析器在template中遭遇一个嵌套从属类型,他便假设这名称不是一个类型,除非你告诉他是。
所以缺省情况下嵌套从属名称不是一个类型. 那你怎么告诉它就是一个类型呢?? 很简单使用我们的typename. 看下面例子:
template<typename C> void prnt2nd(const C& container,C::iterator iter) { if (container.size() >= 2) {} } void main() { list<int> q; list<int>::iterator it; prnt2nd<list<int>>(q,it); system("pause"); }
我们只想验证一下嵌套从属结构是否能逃得过C++编译器的法眼? 我们可以看到它直接在参数当中使用了C::iterator.这里就是一个嵌套从属结构.
我们程序运行,可以发现是会编译出错额. 所以你要让编译器知道他就是一个类型,所以呢,在C::iterator前面加上typename告诉编译器我是一个类
型.这样编译器就不会帮你找事情了. 这样修改 程序就可以跑过了.
#include<iostream> #include<list> using namespace std; template<typename C> void prnt2nd(const C& container,typename C::iterator iter) { if (container.size() >= 2) {} } void main() { list<int> q; list<int>::iterator it; prnt2nd<list<int>>(q,it); system("pause"); }
上述的C并不是嵌套从属类型名称,所以声明container不需要以typename为前导,但是C::iterator是一个从属类型名称. 所以must以typename为
前导. 当然typename也会有特殊情况. "typename必须作为嵌套从属类型名称的前缀词"适用于大多场景,BUT typename不可以出现在base classes list
内的嵌套从属类型名称之前,也不可以在member initalization list(成员初始化列表)中作为base class 修饰符. 代码举例:
//伪代码 template<typename T> class Derived : public Base<T>::Nested { //base class list 中不允许使用"typename" public: explicit Derived(int x) :Base<T>::Nested(x) //可变参数列表中,不允许使用"typename" { typename Base<T>::Nested temp; //这里就需要使用到"typename" } };
这样有时候真的你万一没记起来,也挺烦人的不过呢 ? 一旦你有一点经验也就能勉勉强强接受它.
最后的最后,typename相关规则在不同的编译器上有不同的实践. 某些编译器接受的代码原本该有typename却遗漏了.原本不该有typename却出现了
还有少数的编译器根本就拒绝typename。这意味着typename和"嵌套从属类型名称"之间的互动,也许会在移植性方面给你带来一点小麻烦.
最后总结:
1.声明template参数时,前缀关键字class和typename可互换.
2.请使用关键字typename标识嵌套从属类型名称; 但是不得在base class lists(基类列)或者member initialization list(初始化成员列表)内
以它作为base class修饰符.
相关文章推荐
- 条款42:了解typename的双重意义
- 条款42:了解typename的双重意义
- Effective C++:条款42:了解typename的双重意义
- Effective C++ 条款42 了解typename的双重意义
- 条款42:了解typename的双重意义
- 条款42:了解typename的双重意义
- effective C++ 条款 42:了解typename的双重意义
- 读书笔记《Effective C++》条款42:了解typename的双重意义
- Effective C++ -----条款42:了解typename的双重意义
- Effective C++学习笔记_条款42:了解typename的双重意义
- Item 42: 了解typename的双重意义
- Effective C++ Item 42 了解 typename 的双重意义
- 【42】了解typename的双重意义
- 条款42:了解typename的双重含义
- 《Effective C++》读书笔记之item42:了解typename的双重意义
- 读书笔记_Effective_C++_条款四十二:了解typename的双重意义
- Effective C++ Item 42 了解 typename 的双重意义
- 了解typename的双重意义(Effective C++_42)
- 条款42:了解typename的双重意义。