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的两种意义的更多相关文章

  1. 读书笔记 effective c++ Item 42 理解typename的两种涵义

    1. class和typename含义相同的例子 问题:在下面的模板声明中class和typename的区别是什么? template<class T> class Widget; // ...

  2. 读书笔记 effective c++ Item 49 理解new-handler的行为

    1. new-handler介绍 当操作符new不能满足内存分配请求的时候,它就会抛出异常.很久之前,它会返回一个null指针,一些旧的编译器仍然会这么做.你仍然会看到这种旧行为,但是我会把关于它的讨 ...

  3. 读书笔记 effective c++ Item 30 理解内联的里里外外 (大师入场啦)

    最近北京房价蹭蹭猛涨,买了房子的人心花怒放,没买的人心惊肉跳,咬牙切齿,楼主作为北漂无房一族,着实又亚历山大了一把,这些天晚上睡觉总是很难入睡,即使入睡,也是浮梦连篇,即使亚历山大,对C++的热情和追 ...

  4. 读书笔记 effective c++ Item 41 理解隐式接口和编译期多态

    1. 显示接口和运行时多态 面向对象编程的世界围绕着显式接口和运行时多态.举个例子,考虑下面的类(无意义的类), class Widget { public: Widget(); virtual ~W ...

  5. 读书笔记 effective c++ Item 38 通过组合(composition)为 “has-a”或者“is-implemented-in-terms-of”建模

    1. 什么是组合(composition)? 组合(composition)是一种类型之间的关系,这种关系当一种类型的对象包含另外一种类型的对象时就会产生.举个例子: class Address { ...

  6. 读书笔记 effective c++ Item 45 使用成员函数模板来接受“所有兼容类型”

    智能指针的行为像是指针,但是没有提供加的功能.例如,Item 13中解释了如何使用标准auto_ptr和tr1::shared_ptr指针在正确的时间自动删除堆上的资源.STL容器中的迭代器基本上都是 ...

  7. 读书笔记 effective c++ Item 47 使用traits class表示类型信息

    STL主要由为容器,迭代器和算法创建的模板组成,但是也有一些功能模板.其中之一叫做advance.Advance将一个指定的迭代器移动指定的距离: template<typename IterT ...

  8. 读书笔记 effective c++ Item 21 当你必须返回一个对象的时候,不要尝试返回引用

    1. 问题的提出:要求函数返回对象时,可以返回引用么? 一旦程序员理解了按值传递有可能存在效率问题之后(Item 20),许多人都成了十字军战士,决心清除所有隐藏的按值传递所引起的开销.对纯净的按引用 ...

  9. 读书笔记 effective c++ Item 29 为异常安全的代码而努力

    异常安全在某种意义上来说就像怀孕...但是稍微想一想.在没有求婚之前我们不能真正的讨论生殖问题. 假设我们有一个表示GUI菜单的类,这个GUI菜单有背景图片.这个类将被使用在多线程环境中,所以需要mu ...

随机推荐

  1. oracle 游标的使用

    额,一直提起游标就头疼,总感觉是很高大上的东西,望而却步... 今天要做的东西涉及到了实时更新数据,要用到JOB 存储过程  游标 通过在网上查资料,请教同事,也开始继续深入oracle,,,,小菜啊 ...

  2. Spring MVC 教程(比较全的一篇文章了)

    http://elf8848.iteye.com/blog/875830 11年1月份的文章,但是Spring3,现在是4,不过还是很实用

  3. c语言中,有符号数位移

    #include <stdio.h> int main(void) { unsigned i = 0xcffffff3; long j=0xcffffff3; int k=0xcfffff ...

  4. [原创.数据可视化系列之十三]idw反距离权重插值算法的javascript代码实现

    图形渲染中,idw反距离权重插值算法是一个应用非常广泛的方法,但是js实现的比较少,目前实现一个: //idw算法 //输入[[x:0,y:0,v:0],[x:0,y:0,v:0],[x:0,y:0, ...

  5. JNI调用的helloworld(JNI_OnLoad映射方式)

    本示例展示JNI的基本示例,helloworld级别的,不过是用JNI_OnLoad映射的方式. 直接看代码,先看包含native method的Person.java的代码: package hel ...

  6. 规范 : angular ui router path & params

    在seo文章中提到url的path 必须是 why-us,而不是whyUS 所以定了规范,所有的path 必须why-us path ?后尾的是用来filter的,所以可以WhyUs 如果是不需要给s ...

  7. ios的300ms点击延时问题

    一.什么是ios的300ms点击延时问题 ios的移动端页面对点击事件有300ms延时. 二.为什么存在这个问题 这要追溯至 2007 年初.苹果公司在发布首款 iPhone 前夕,遇到一个问题 —— ...

  8. 2818: Gcd

    2818: Gcd Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 2170  Solved: 979[Submit][Status][Discuss] ...

  9. react router路径的匹配原则

    路由匹配规则是从上到下执行,一旦发现匹配,就不再其余的规则了. (1):paramName :paramName匹配URL的一个部分,直到遇到下一个/.?.#为止.这个路径参数可以通过this.pro ...

  10. iOS多线程——GCD

    最近的项目遇到了很多多线程的问题,借此机会对GCD进行了一番学习并总结.首先说一下什么是GCD,GCD全称 Grand Central Dispatch,是异步执行任务的技术之一.开发者只需要定义想要 ...