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 慢查询

    一.查询执行最慢的sql select * from (select sa.SQL_TEXT, sa.SQL_FULLTEXT, sa.EXECUTIONS "执行次数", , ) ...

  2. hao123劫持主页

    转自:https://www.zhihu.com/question/39881858 第一步 Firefox.打开Firefox,按Alt+H弹出帮助菜单,点“故障排除信息” 第二步 点“配置文件夹” ...

  3. Longest Word in Dictionary through Deleting - LeetCode

    目录 题目链接 注意点 解法 小结 题目链接 Longest Word in Dictionary through Deleting - LeetCode 注意点 长度一样的字符串要按字典序返回较小的 ...

  4. 一步步创建第一个Docker App —— 2. 创建 Docker化 主机

    原文:https://docs.docker.com/engine/getstarted-voting-app/node-setup/ 部署voting app的第一步,是为集群节点(swarm no ...

  5. BZOJ3834 [Poi2014]Solar Panels 【数论】

    题目链接 BZOJ3834 题解 容易想到对于\(gcd(x,y) = D\),\(d\)的倍数一定存在于两个区间中 换言之 \[\lfloor \frac{a - 1}{D} \rfloor < ...

  6. Java中的三目运算符可能出现的问题

    你真的了解Java中的三目运算符吗? 原创 2018-04-27 刨根问底的 Hollis Hollis Hollis 微信号 hollischuang 功能介绍 一个对Coding有着独特追求的人. ...

  7. 前端学习 -- Css -- 兄弟元素选择器

    为一个元素后边的元素设置css样式: 语法:前一个 + 后一个. 作用:可以选中一个元素后紧挨着的指定的兄弟元素. 为一个元素后边的所有相同元素设置css样式: 语法:前一个 ~ 后边所有. < ...

  8. bzoj 2178 自适应Simpson积分

    #include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #i ...

  9. containerdns配置说明

    使用containerdns的理由 先说下我们为什么要使用containerdns,事实上该项目开源时间并不长,而且没有完善的社区,也没有丰富的文档.说白了,我们选中它,是因为它刚好切合我们的需求. ...

  10. numpy计算路线距离

    numpy计算路线距离 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献 enumerate遍历数组 np.diff函数 numpy适用数组作为索引 标记路线上的点 \[X={X1,X ...