C++箴言:领略typename的两个寄义
当前位置:以往代写 > C/C++ 教程 >C++箴言:领略typename的两个寄义
2019-06-13

C++箴言:领略typename的两个寄义

C++箴言:领略typename的两个寄义

副标题#e#

问题:在下面的 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(嵌套依赖名字)。


#p#副标题#e#

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(范例)。(对付这条法则有一个破例,我待会儿汇报你。)

#p#副标题#e#

记着这个,再看看 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

#p#分页标题#e#

这为什么不是正当的 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 前置。

#p#副标题#e#

"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 前置。

#p#副标题#e#

假如你以为读 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);
 ...
}

#p#分页标题#e#

许多措施员最初发明 "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(基类标识符)时除外。

    关键字:

在线提交作业