关于C++中模板中的typename和class的区别⽐较
问题:在下⾯的 template declarations(模板声明)中 class 和 typename 有什么不同?
template<class T> class Widget; // uses "class"
丰田普拉多4.0油耗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)
{
tfsi是什么意思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
6450btypename 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(基类标识符)时除外。
发布评论