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

小编 2026-06-23 阅读:338 评论:0
1. class和typename含义相同的例子问题:在下面的模板声明中class和typena...

1. class和typename含义相同的例子

问题:在下面的模板声明中class和typename的区别是什么?

1 template<class T> class Widget;     // uses “class”2 3 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类型。进一步假设这个函数打印容器中的第二个元素值。我在下面以愚蠢的方式实现了一个愚蠢的函数,它甚至不能通过编译,但是请忽略这些事情,看下面的例子: 

 

 1 template<typename C> // print 2nd element in 2 void print2nd(const C& container) // container; 3 { // this is not valid C++! 4 if (container.size() >= 2) { 5 C::const_iterator iter(container.begin()); // get iterator to 1st element 6 ++iter; // move iter to 2nd element 7 int value = *iter; // copy that element to an int 8  9 std::cout << value;                       // print the int10 11 }                                                 12 13 }     

                                    

 我对此函数中的两个本地变量做了高亮,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函数以下面的方式开始,会更加愚蠢:

 1 template<typename C> 2  3 void print2nd(const C& container) 4  5 { 6  7 C::const_iterator * x; 8  9 ...10 11 }

 

看上去像是我们声明了一个本地变量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的开始部分:

1 template<typename C>2 void print2nd(const C& container)3 {4 if (container.size() >= 2) {5 C::const_iterator iter(container.begin()); // this name is assumed to6 ... // not be a type

 

现在应该清楚为什么这不是有效的C++了。Iter的声明只有在C::const_iterator是一个type的情况下才有意义,但是我们并没有告知C++它是一个类型,于是C++假设它不是一个类型。为了纠正这种情况,我们必须告诉C++ C::const_iterator是一个类型。我们将typename放在type之前就能达到这个目的:

 1 template<typename C>                                                               // this is valid C++ 2  3 void print2nd(const C& container)                                             4  5 {                                                                                                 6  7 if (container.size() >= 2) {                                                           8  9 typename C::const_iterator iter(container.begin());                  10 11 ...                                                                                                12 13 }                                                                                                14 15 }

 

这个规则很简单:在一个模板中,任何时候你引用一个内嵌依赖类型名字,你都必须在名字前加上typename。(也有例外,一会会提到。)

 

typename应该只被用来确认一个内嵌依赖类型名字;其他的名字不应该加这个前缀。例如,下面的函数模板使用两个参数,一个容器和一个容器的迭代器:

                                                                                                  

1 template<typename C>                        // typename allowed (as is “class”)2 void f(const C& container, // typename not allowed3 typename C::iterator iter); // typename required

 

C不是内嵌依赖类型名字(它没有内嵌在任何依赖于模板参数的东西中),所以在声明容器的时候不应该加typename,但是C::iterator是一个内嵌依赖类型名字,所以需要加typename。

 

3. 一个例外——不能使用typename的地方

 

”typename”必须加在内嵌依赖类型名字之前“这个规则有一个例外:基类列表中的内嵌依赖类型名字或者成员初始化列表中的基类标识符不能加typename。例如:

 1 template<typename T> 2 class Derived: public Base<T>::Nested { // base class list: typename not 3  4 public:                                    // allowed 5  6 explicit Derived(int x)            7  8   9 10 : Base<T>::Nested(x)          // base class identifier in mem.11 12 {                                         // init. list: typename not allowed13 14 15 typename Base<T>::Nested temp; // use of nested dependent type16 ... // name not in a base class list or17 } // as a base class identifier in a18 ... // mem. init. list: typename19 required20 };

 

这种不一致性令人感到厌烦,但是一旦你有了一点经验,你就会注意到它。

 

4. 最后的例子——为typename使用typedef

 

让我们看最后一个typename的例子,因为它代表了你将会在真实代码中看到的某些东西。假设我们正在实现一个函数模板,带了一个迭代器参数,我们想为迭代器指向的对象做一份本地拷贝,temp。我们可以像下面这样实现:

 

1 template<typename IterT>2 void workWithIterator(IterT iter)3 {4 typename std::iterator_traits<IterT>::value_type temp(*iter);5 ...6 }

 

不要让 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通常被定义成下面这样:

1 template<typename IterT>2 void workWithIterator(IterT iter)3 {4 typedef typename std::iterator_traits<IterT>::value_type value_type;5 value_type temp(*iter);6 ...7 }

 

许多程序员发现将“typedef typename“并列看上去不和谐,但是对于使用内嵌依赖类型名字的规则来说,这是一个合乎逻辑的结果。你会很快习惯这种用法。毕竟,你有着很强的驱动力。你想输入typename std::iterator_traits<IterT>::value_type多少次呢?

5. Typename的执行因编译器而异

作为结束语,我应该提及的是关于typename规则的强制执行随着编译器的不同而不同,一些编译器接受需要typename但实际上没有输入的情况;一些编译器接受输入了typename但实际上不允许的情况;还有一些(通常是老的编译器)在需要输入typename时拒绝了typename输入。这就意味着typename和内嵌依赖类型名字的交互会产生让你头痛的问题。

6. 总结

  • 当声明模板参数的时候,class和typename是可以互换的。
  • 使用typename来识别内嵌依赖类型名字,但在基类列表中或者成员初始化列表中的基类标识符除外。


作者:HarlanC

博客地址:http://www.cnblogs.com/harlanc/
个人博客: http://www.harlancn.me/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接

如果觉的博主写的可以,收到您的赞会是很大的动力,如果您觉的不好,您可以投反对票,但麻烦您留言写下问题在哪里,这样才能共同进步。谢谢!

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

热门文章
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
标签列表