`
1025250620
  • 浏览: 229997 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

C++箴言:理解typename的两个含义

    博客分类:
  • c++
 
阅读更多

 问题:在下面的 template declarations(模板声明)中 class 和 typename 有什么不同? 

template<class T> class Widget; // uses "class"
template<typename T> class Widget; // uses "typename"

  答案:没什么不同。在声明一个 template type parameter(模板类型参数)的时候,class 和 typename 意味着完全相同的东西。一些程序员更喜欢在所有的时间都用 class,因为它更容易输入。其他人(包括我本人)更喜欢 typename,因为它暗示着这个参数不必要是一个 class type(类类型)。少数开发者在任何类型都被允许的时候使用 typename,而把 class 保留给仅接受 user-defined types(用户定义类型)的场合。但是从 C++ 的观点看,class 和 typename 在声明一个 template parameter(模板参数)时意味着完全相同的东西。

  然而,C++ 并不总是把 class 和 typename 视为等同的东西。有时你必须使用 typename。为了理解这一点,我们不得不讨论你会在一个 template(模板)中涉及到的两种名字。

  假设我们有一个函数的模板,它能取得一个 STL-compatible container(STL 兼容容器)中持有的能赋值给 ints 的对象。进一步假设这个函数只是简单地打印它的第二个元素的值。它是一个用糊涂的方法实现的糊涂的函数,而且就像我下面写的,它甚至不能编译,但是请将这些事先放在一边——有一种方法能发现我的愚蠢:

template<typename C> // print 2nd element in
void print2nd(const C& container) // container;

 // this is not valid C++!
 if (container.size() >= 2) {
  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
 }
}

  我突出了这个函数中的两个 local variables(局部变量),iter 和 value。iter 的类型是 C::const_iterator,一个依赖于 template parameter(模板参数)C 的类型。一个 template(模板)中的依赖于一个 template parameter(模板参数)的名字被称为 dependent names(依赖名字)。当一个 dependent names(依赖名字)嵌套在一个 class(类)的内部时,我称它为 nested dependent name(嵌套依赖名字)。C::const_iterator 是一个 nested dependent name(嵌套依赖名字)。实际上,它是一个 nested dependent type name(嵌套依赖类型名),也就是说,一个涉及到一个 type(类型)的 nested dependent name(嵌套依赖名字)。

  print2nd 中的另一个 local variable(局部变量)value 具有 int 类型。int 是一个不依赖于任何 template parameter(模板参数)的名字。这样的名字以 non-dependent names(非依赖名字)闻名。(我想不通为什么他们不称它为 independent names(无依赖名字)。如果,像我一样,你发现术语 "non-dependent" 是一个令人厌恶的东西,你就和我产生了共鸣,但是 "non-dependent" 就是这类名字的术语,所以,像我一样,转转眼睛放弃你的自我主张。)

  nested dependent name(嵌套依赖名字)会导致解析困难。例如,假设我们更加愚蠢地以这种方法开始 print2nd:

template<typename C>
void print2nd(const C& container)
{
 C::const_iterator * x;
 ...
}

  这看上去好像是我们将 x 声明为一个指向 C::const_iterator 的 local variable(局部变量)。但是它看上去如此仅仅是因为我们知道 C::const_iterator 是一个 type(类型)。但是如果 C::const_iterator 不是一个 type(类型)呢?如果 C 有一个 static data member(静态数据成员)碰巧就叫做 const_iterator 呢?再如果 x 碰巧是一个 global variable(全局变量)的名字呢?在这种情况下,上面的代码就不是声明一个 local variable(局部变量),而是成为 C::const_iterator 乘以 x!当然,这听起来有些愚蠢,但它是可能的,而编写 C++ 解析器的人必须考虑所有可能的输入,甚至是愚蠢的。

  直到 C 成为已知之前,没有任何办法知道 C::const_iterator 到底是不是一个 type(类型),而当 template(模板)print2nd 被解析的时候,C 还不是已知的。C++ 有一条规则解决这个歧义:如果解析器在一个 template(模板)中遇到一个 nested dependent name(嵌套依赖名字),它假定那个名字不是一个 type(类型),除非你用其它方式告诉它。缺省情况下,nested dependent name(嵌套依赖名字)不是 types(类型)。(对于这条规则有一个例外,我待会儿告诉你。)

  记住这个,再看看 print2nd 的开头:

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

  这为什么不是合法的 C++ 现在应该很清楚了。iter 的 declaration(声明)仅仅在 C::const_iterator 是一个 type(类型)时才有意义,但是我们没有告诉 C++ 它是,而 C++ 就假定它不是。要想转变这个形势,我们必须告诉 C++ C::const_iterator 是一个 type(类型)。我们将 typename 放在紧挨着它的前面来做到这一点:

template<typename C> // this is valid C++
void print2nd(const C& container)
{
if (container.size() >= 2) {
typename C::const_iterator iter(container.begin());
...
}
}

  通用的规则很简单:在你涉及到一个在 template(模板)中的 nested dependent type name(嵌套依赖类型名)的任何时候,你必须把单词 typename 放在紧挨着它的前面。(重申一下,我待会儿要描述一个例外。)

  typename 应该仅仅被用于标识 nested dependent type name(嵌套依赖类型名);其它名字不应该用它。例如,这是一个取得一个 container(容器)和这个 container(容器)中的一个 iterator(迭代器)的 function template(函数模板):

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

  C 不是一个 nested dependent type name(嵌套依赖类型名)(它不是嵌套在依赖于一个 template parameter(模板参数)的什么东西内部的),所以在声明 container 时它不必被 typename 前置,但是 C::iterator 是一个 nested dependent type name(嵌套依赖类型名),所以它必需被 typename 前置。

  "typename must precede nested dependent type names"(“typename 必须前置于嵌套依赖类型名”)规则的例外是 typename 不必前置于在一个 list of base classes(基类列表)中的或者在一个 member initialization list(成员初始化列表)中作为一个 base classes identifier(基类标识符)的 nested dependent type name(嵌套依赖类型名)。例如:

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
};

  这样的矛盾很令人讨厌,但是一旦你在经历中获得一点经验,你几乎不会在意它。

  让我们来看最后一个 typename 的例子,因为它在你看到的真实代码中具有代表性。假设我们在写一个取得一个 iterator(迭代器)的 function template(函数模板),而且我们要做一个 iterator(迭代器)指向的 object(对象)的局部拷贝 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(标准特性类)的使用,用 C++ 的说法就是 "the type of thing pointed to by objects of type IterT"(“被类型为 IterT 的对象所指向的东西的类型”)。这个语句声明了一个与 IterT objects 所指向的东西类型相同的 local variable(局部变量)(temp),而且用 iter 所指向的 object(对象)对 temp 进行了初始化。如果 IterT 是 vector<int>::iterator,temp 就是 int 类型。如果 IterT 是 list<string>::iterator,temp 就是 string 类型。因为 std::iterator_traits<IterT>::value_type 是一个 nested dependent type name(嵌套依赖类型名)(value_type 嵌套在 iterator_traits<IterT> 内部,而且 IterT 是一个 template parameter(模板参数)),我们必须让它被 typename 前置。

  如果你觉得读 std::iterator_traits<IterT>::value_type 令人讨厌,就想象那个与它相同的东西来代表它。如果你像大多数程序员,对多次输入它感到恐惧,那么你就需要创建一个 typedef。对于像 value_type 这样的 traits member names(特性成员名),一个通用的惯例是 typedef name 与 traits member name 相同,所以这样的一个 local typedef 通常定义成这样:

template<typename IterT>
void workWithIterator(IterT iter)
{
 typedef typename std::iterator_traits<IterT>::value_type value_type;

 value_type temp(*iter);
 ...
}

  很多程序员最初发现 "typedef typename" 并列不太和谐,但它是涉及 nested dependent type names(嵌套依赖类型名)规则的一个合理的附带结果。你会相当快地习惯它。你毕竟有着强大的动机。你输入 typename std::iterator_traits<IterT>::value_type 需要多少时间?

  作为结束语,我应该提及编译器与编译器之间对围绕 typename 的规则的执行情况的不同。一些编译器接受必需 typename 时它却缺失的代码;一些编译器接受不许 typename 时它却存在的代码;还有少数的(通常是老旧的)会拒绝 typename 出现在它必需出现的地方。这就意味着 typename 和 nested dependent type names(嵌套依赖类型名)的交互作用会导致一些轻微的可移植性问题。

  Things to Remember

  ·在声明 template parameters(模板参数)时,class 和 typename 是可互换的。

  ·用 typename 去标识 nested dependent type names(嵌套依赖类型名),在 base class lists(基类列表)中或在一个 member initialization list(成员初始化列表)中作为一个 base class identifier(基类标识符)时除外。
分享到:
评论

相关推荐

    编程中国-C++箴言:理解typename的两个含义.txt

    在深入探讨C++中`typename`关键字的双层含义之前,我们先来简要回顾一下C++...通过本文的讲解,相信读者已经对`typename`的两个核心含义有了深刻的理解,这将有助于在实际开发中避免因类型解析错误而引发的编译问题。

    C++箴言:将强制转型减到最少

    ### C++箴言:将强制转型减到最少 #### 引言 在C++编程中,类型系统的健全性是确保程序正确性和健壮性的基石。然而,在某些情况下,程序员可能会遇到需要进行类型转换的需求,而这些转换往往伴随着潜在的风险与...

    深入理解C++11:C++11新特性解析与应用(高清62M扫描加配套源码)

    《深入理解C++11:C++11新特性解析与应用》这本书是C++编程领域的一本重要著作,旨在帮助读者全面掌握C++11标准引入的新特性,并将其有效地应用到实际项目中。C++11是C++语言的一个重大更新,它引入了大量的新功能和...

    C++ Templates: The Complete Guide 英文版

    模板是C++语言中一个强大的特性,它允许程序员编写泛型代码,以实现代码复用和高效编程。下面我们将深入探讨C++模板的相关知识点。 1. 模板的基本概念 - 类模板:用于创建具有通用功能的类,如STL中的容器(如...

    深入理解c++11pdf

    《深入理解C++11》是一本专注于C++11标准新特性的解析与应用的书籍,其中涉及了语言的诸多改进和优化。在第二章中,主要讨论了如何通过`final`和`override`关键字确保类的稳定性和兼容性。 在C++11之前,多态性依赖...

    深入理解C++11:C++11新特性解析与应用

    《深入理解C++11:C++11新特性解析与应用》这本书是C++开发者探索新标准的重要参考资料。C++11是C++编程语言的一次重大更新,引入了许多新特性,旨在提高效率,增强安全性,并简化代码。以下是C++11中的一些关键特性...

    阅读材料-C++ 模板中的typename、class关键字.rar_C++ typename_class

    本文将深入探讨这两个关键字的含义和用法。 ### 1. `typename` 关键字 `typename`关键字在模板中用于声明依赖于模板参数的类型成员。当我们在模板中引用一个依赖于模板参数的类型成员时,编译器无法确定这个成员...

    C++模板:进阶特性的探索

    这里的`typename`关键词表示`T`是一个类型,也可以使用`class`替代`typename`。`T`是模板参数,代表任意类型。当我们在程序中调用这个模板函数时,编译器会根据传递的参数类型自动生成相应的函数实例。例如: ```...

    C++ 入门:类的编写

    5. **继承**:C++支持单一继承,一个类可以从另一个类派生,形成类的层次结构。继承允许子类继承父类的属性和行为: ```cpp class DerivedClass : public BaseClass { // 子类可以访问基类的公有和保护成员 }; ```...

    C++开发:栈的生成与应用

    通过阅读和分析这个源代码,你可以深入理解C++中的栈操作和类模板的应用。此外,还可以尝试修改代码,增加新的功能,例如支持栈的合并、复制栈或查找栈中的特定元素,以进一步提升对C++栈的理解。

    详解C++的模板中typename关键字的用法

    接下来是`typename`关键字的两个主要用法: 用处1: 用在模板定义里,标明其后的模板参数是类型参数 当定义模板时,通常我们使用`class`关键字来指定一个类型参数。实际上,`class`和`typename`在模板定义中可以互换...

    C++中的std::async()详解

    future&lt;typename&gt;::type&gt; async(launch policy, Fn&& fn, Args&&...args); 功能:第二个参数接收一个可调用对象(仿函数、lambda表达式、类成员函数、普通函数……)作为参数,并且异步或是同步执行他们。 a、对于是...

    Why C++ Supports typename and class.txt

    根据标题"Why C++ Supports typename and class"以及描述中提到的“不错的关于C++中typedef和class的使用说明”,我们可以详细分析这两个关键词在C++中的作用和历史背景。 ### `typename`与`class`:模板参数的指示...

    C++多线程封装成类使用示例

    本示例将详细介绍如何将C++的多线程功能封装成一个类,以便在实际项目中更加灵活地使用。 首先,我们需要了解C++11中的`std::thread`类。`std::thread`允许我们创建和管理线程。创建一个新线程的基本步骤包括声明一...

    深入理解C++11

    10. **变长参数模板(Variadic Templates)**:允许模板参数列表包含零个或多个参数,如`template&lt;typename... Args&gt; void func(Args... args);`,这为泛型编程提供了更大的灵活性。 11. **静态_assert**:在编译时...

    C++66个关键字的中文含义

    ### C++66个关键字的中文含义详解 #### asm(汇编) - **用途**:允许在C++程序中嵌入汇编代码。 - **语法**:`asm(指令字符串);` - **示例**:如果需要直接控制硬件或者优化性能,可以在C++中插入汇编代码。 ```...

    深入理解c++模板中的class与typename

    在C++模板编程中,`class`和`typename`关键字看似可以互换,但实际上它们在特定情况下有着重要的区别。在声明模板时,`class`和`typename`都可以用来指定模板参数,但涉及到从属类型(dependent type)时,两者的...

    C语言32以及C++63个关键字及其含义[定义].pdf

    C语言32个关键字及其含义和C++63个关键字及其含义 C语言32个关键字及其含义: 1. auto:自动变量用关键字auto作存储类别的声明。(可以省略,不写则隐含确定为“自动存储类别”) 2. break:不能用于循环语句和...

Global site tag (gtag.js) - Google Analytics