读书笔记 effective c++ Item 42 理解typename的两种涵义
1. class和typename含义相同的例子
问题:在下面的模板声明中class和typename的区别是什么?
template<class T> class Widget; // uses “class” template<typename T> class Widget; // uses “typename”
答案:没有任何区别。当声明一个模板类型参数时,class和typename意味着相同的事情。一些程序员喜欢使用class,因为容易敲打。其他的(包括我)更加喜欢使用typename,因为用它表明参数不需要是一个class类型。一些程序员在允许使用任何type的时候使用typename,只用对用户自定义的类型使用class。但是从C++ 的观点来看,在声明模板参数的时候class和typename意味着相同的事情。
2. 必须使用typename的例子
然而,C++并不总是将class和typename同等对待。有时你必须使用typename。为了理解在什么时候必须使用,我们必须讨论能够在模板中引用的两种名字。
假设我们有一个函数模板,用和STL兼容的容器作为模板参数,此容器中包含的对象能够被赋值给int类型。进一步假设这个函数打印容器中的第二个元素值。我在下面以愚蠢的方式实现了一个愚蠢的函数,它甚至不能通过编译,但是请忽略这些事情,看下面的例子:
template<typename C> // print 2nd element in
void print2nd(const C& container) // container;
{ // this is not valid C++!
if (container.size() >= ) {
C::const_iterator iter(container.begin()); // get iterator to 1st element
++iter; // move iter to 2nd element
int value = *iter; // copy that element to an int std::cout << value; // print the int } }
我对此函数中的两个本地变量做了高亮,iter和value。Iter的类型是C::const_iterator,它依赖于模板参数C。模板中依赖于模板参数的名字被称作依赖名字(dependent names)。当一个依赖名字嵌套在一个类中的时候,我把它叫做内嵌依赖名字(nested dependent name)。C::const_iterator是一个内嵌依赖名字。事实上,它是一个内嵌依赖类型名字(nested dependent type name),也即是指向一个类型(type)的内嵌依赖名字。
对于print2nd中的其他本地变量,value,类型为int。int不依赖于任何模板参数。这种名字被称作“非依赖名字”(non-dependent names)。(我不知道为什么不把它们叫做独立名字(independent names)。“non-dependent”是一种不好的命名方式,但毕竟它是术语,所以需要遵守这个约定。)
内嵌依赖名字会导致解析困难。例如,如果我们让print2nd函数以下面的方式开始,会更加愚蠢:
template<typename C>
void print2nd(const C& container)
{
C::const_iterator * x;
...
}
看上去像是我们声明了一个本地变量x,这个x指针指向一个C::const_iterator。但是它看上去是这样的仅仅因为我们“知道”C::const_iterator是一个type。但是如果C::const_iterator不是一个type会是怎样呢?如果C有个静态数据成员恰好被命名为const_iterator会发生什么?如果x恰巧是一个全局变量的名字呢?在这种情况下,上面的code就不会声明一个本地变量,它会是C::const_iterator和x的乘积!听起来有些疯狂,但这是可能的,实现C++编译器的人员也必须考虑到所有可能的输入,包括一些看起来很疯狂的例子。
直到C被确定之前,没有办法知道C::const_iterator是否是一个type,当函数模板print2nd被解析的时候,C不能够被确认。为了处理这种模棱两可的问题,C++有一个准则:如果解析器在模板中碰到了一个内嵌依赖名字,它不会认为这是一个type,除非你告诉它。默认情况下,内嵌依赖名字不是types。(对于这个规则有个例外,一会会提到。)
将上面的规则记在心中,再看一次print2nd的开始部分:
template<typename C>
void print2nd(const C& container)
{
if (container.size() >= ) {
C::const_iterator iter(container.begin()); // this name is assumed to
... // not be a type
现在应该清楚为什么这不是有效的C++了。Iter的声明只有在C::const_iterator是一个type的情况下才有意义,但是我们并没有告知C++它是一个类型,于是C++假设它不是一个类型。为了纠正这种情况,我们必须告诉C++ C::const_iterator是一个类型。我们将typename放在type之前就能达到这个目的:
template<typename C> // this is valid C++
void print2nd(const C& container)
{
if (container.size() >= ) {
typename C::const_iterator iter(container.begin());
...
}
}
这个规则很简单:在一个模板中,任何时候你引用一个内嵌依赖类型名字,你都必须在名字前加上typename。(也有例外,一会会提到。)
typename应该只被用来确认一个内嵌依赖类型名字;其他的名字不应该加这个前缀。例如,下面的函数模板使用两个参数,一个容器和一个容器的迭代器:
template<typename C> // typename allowed (as is “class”)
void f(const C& container, // typename not allowed
typename C::iterator iter); // typename required
C不是内嵌依赖类型名字(它没有内嵌在任何依赖于模板参数的东西中),所以在声明容器的时候不应该加typename,但是C::iterator是一个内嵌依赖类型名字,所以需要加typename。
3. 一个例外——不能使用typename的地方
”typename”必须加在内嵌依赖类型名字之前“这个规则有一个例外:基类列表中的内嵌依赖类型名字或者成员初始化列表中的基类标识符不能加typename。例如:
template<typename T>
class Derived: public Base<T>::Nested { // base class list: typename not public: // allowed explicit Derived(int x) : Base<T>::Nested(x) // base class identifier in mem. { // init. list: typename not allowed typename Base<T>::Nested temp; // use of nested dependent type
... // name not in a base class list or
} // as a base class identifier in a
... // mem. init. list: typename
required
};
这种不一致性令人感到厌烦,但是一旦你有了一点经验,你就会注意到它。
4. 最后的例子——为typename使用typedef
让我们看最后一个typename的例子,因为它代表了你将会在真实代码中看到的某些东西。假设我们正在实现一个函数模板,带了一个迭代器参数,我们想为迭代器指向的对象做一份本地拷贝,temp。我们可以像下面这样实现:
template<typename IterT>
void workWithIterator(IterT iter)
{
typename std::iterator_traits<IterT>::value_type temp(*iter);
...
}
不要让 std::iterator_traits<IterT>::value_type 吓到你。这只是标准特性类(standard traits class)的一种使用方法,这是“类型IterT对象指向的类型“的C++实现方式。这个句子声明了一个本地变量(temp),它的类型同IterT对象指向的对象的类型一致,它将temp初始化为iter指向的对象。如果IterT是vector<int>::iterator,那么temp就是int类型的。如果IterT是list<string>::iterator,temp就是string类型的。因为std::iterator_traits<IterT>::value_type是一个内嵌依赖类型名字(在iterator_traits<IterT>内部value_type是内嵌的,IterT是一个模板参数),我们必须为其添加typename。
如果你认为读std::iterator_traits<IterT>::value_type是一件不让人愉快的事情,想像一下将其打出来会是什么样的。如果你像大部分程序员一样,多次输入这个表达式的想法是可怕的,所以你会想为其创建一个typedef。对于像value_type这样的特性(traits)成员名字来说(对于特性的信息看Item47),使用惯例是使得typedef名字和特性成员名字相同,所以这样一个本地typedef通常被定义成下面这样:
template<typename IterT>
void workWithIterator(IterT iter)
{
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
...
}
许多程序员发现将“typedef typename“并列看上去不和谐,但是对于使用内嵌依赖类型名字的规则来说,这是一个合乎逻辑的结果。你会很快习惯这种用法。毕竟,你有着很强的驱动力。你想输入typename std::iterator_traits<IterT>::value_type多少次呢?
5. Typename的执行因编译器而异
作为结束语,我应该提及的是关于typename规则的强制执行随着编译器的不同而不同,一些编译器接受需要typename但实际上没有输入的情况;一些编译器接受输入了typename但实际上不允许的情况;还有一些(通常是老的编译器)在需要输入typename时拒绝了typename输入。这就意味着typename和内嵌依赖类型名字的交互会产生让你头痛的问题。
6. 总结
- 当声明模板参数的时候,class和typename是可以互换的。
- 使用typename来识别内嵌依赖类型名字,但在基类列表中或者成员初始化列表中的基类标识符除外。
读书笔记 effective c++ Item 42 理解typename的两种涵义的更多相关文章
- 读书笔记 effective c++ Item 42 理解typename的两种意义
1. class和typename意义相同的例子 问题:在下面的模板声明中class和typename的区别是什么? template<class T> class Widget; // ...
- 读书笔记 effective c++ Item 49 理解new-handler的行为
1. new-handler介绍 当操作符new不能满足内存分配请求的时候,它就会抛出异常.很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做.你仍然会看到这种旧行为,但是我会把关于它的讨 ...
- 读书笔记 effective c++ Item 30 理解内联的里里外外 (大师入场啦)
最近北京房价蹭蹭猛涨,买了房子的人心花怒放,没买的人心惊肉跳,咬牙切齿,楼主作为北漂无房一族,着实又亚历山大了一把,这些天晚上睡觉总是很难入睡,即使入睡,也是浮梦连篇,即使亚历山大,对C++的热情和追 ...
- 读书笔记 effective c++ Item 41 理解隐式接口和编译期多态
1. 显示接口和运行时多态 面向对象编程的世界围绕着显式接口和运行时多态.举个例子,考虑下面的类(无意义的类), class Widget { public: Widget(); virtual ~W ...
- 读书笔记 effective c++ Item 38 通过组合(composition)为 “has-a”或者“is-implemented-in-terms-of”建模
1. 什么是组合(composition)? 组合(composition)是一种类型之间的关系,这种关系当一种类型的对象包含另外一种类型的对象时就会产生.举个例子: class Address { ...
- 读书笔记 effective c++ Item 45 使用成员函数模板来接受“所有兼容类型”
智能指针的行为像是指针,但是没有提供加的功能.例如,Item 13中解释了如何使用标准auto_ptr和tr1::shared_ptr指针在正确的时间自动删除堆上的资源.STL容器中的迭代器基本上都是 ...
- 读书笔记 effective c++ Item 47 使用traits class表示类型信息
STL主要由为容器,迭代器和算法创建的模板组成,但是也有一些功能模板.其中之一叫做advance.Advance将一个指定的迭代器移动指定的距离: template<typename IterT ...
- 读书笔记 effective c++ Item 21 当你必须返回一个对象的时候,不要尝试返回引用
1. 问题的提出:要求函数返回对象时,可以返回引用么? 一旦程序员理解了按值传递有可能存在效率问题之后(Item 20),许多人都成了十字军战士,决心清除所有隐藏的按值传递所引起的开销.对纯净的按引用 ...
- 读书笔记 effective c++ Item 29 为异常安全的代码而努力
异常安全在某种意义上来说就像怀孕...但是稍微想一想.在没有求婚之前我们不能真正的讨论生殖问题. 假设我们有一个表示GUI菜单的类,这个GUI菜单有背景图片.这个类将被使用在多线程环境中,所以需要mu ...
随机推荐
- Ld, -rpath, -rpath-link
http://blog.csdn.net/xph23/article/details/38157491
- 为什么redis支持lua脚本功能
看看大家怎么说! 参考: (1)描述界面:WOW和剑网三的界面都是用LUA写的: (2)沟通引擎:游戏图形引擎提供了一些接口库,可以在LUA中调用: (3)服务器端:有些游戏,例如剑网三,在服务器端也 ...
- 3532: [Sdoi2014]Lis 最小字典序最小割
3532: [Sdoi2014]Lis Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 865 Solved: 311[Submit][Status] ...
- PHP正则表达式函数学习
正则表达式是在日常开发中经常用到的,通常一些使用频率过高的正则表达式都是直接粘贴复制,对于基础正则的使用还是要铭记于心的,今天抽时间整理一些php正则表达式的用法. 一.php中常用的正则表达式函数 ...
- static的应用
静态变量.静态代码块.静态方法.非静态方法的区别: 执行顺序:静态变量和静态代码块是按照先后顺序执行的,即在类加载的时候就执行的,属于自动执行的.使用场景:就是一些全局常量,在开始的时候就需要加载的. ...
- aapt命令说明
这里借用一下百度百科,我比较懒 1.列出apk包的内容 aapt l[ist] [-v] [-a] <你的应用> -v 以table形式列出来 -a 详细列出内容 例如:aapt l &l ...
- DHCP 服务器功能
DHCP服务器不仅可以分配IP地址,同时也可以分配网关和DNS服务器地址
- 装饰器 and 闭包函数 未完。。。。。
装饰器是一个返回函数的高阶函数.装饰器=高阶函数+函数嵌套+闭包 装饰器需要遵循的原则:不修改被装饰函数的源代码,不修改被装饰函数的调用方式. 高阶函数 1.函数接收的参数是一个函数名 2.函数的返回 ...
- linux下常用的几个时间函数:time,gettimeofday,clock_gettime,_ftime
time()提供了秒级的精确度 1.头文件 <time.h> 2.函数原型 time_t time(time_t * timer) 函数返回从TC1970-1-1 0:0:0开始到现在的秒 ...
- 【测试笔记】Redis学习笔记(十二)性能测试
http://blog.csdn.net/yangcs2009/article/details/50781530 Redis测试服务器一 redis_version:2.8.4 www@iZ23s8a ...