> 文章列表 > Effective C++条款条款42:了解typename的双重意义(Understand the two meanings of typename)

Effective C++条款条款42:了解typename的双重意义(Understand the two meanings of typename)

Effective C++条款条款42:了解typename的双重意义(Understand the two meanings of typename)

Effective C++条款条款42:了解typename的双重意义(Understand the two meanings of typename)

  • 条款42:了解typename的双重意义
    • 1、从属名称和非从属名称
    • 2、typename在traits机制中的运用
    • 3、牢记
  • 总结

《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:

第7章:模板与泛型编程

Effective C++条款条款42:了解typename的双重意义(Understand the two meanings of typename)


条款42:了解typename的双重意义

  观察一下,下列template申明式中,class和typename有什么不同?

template<class T> class Widget;
template<typename T> class Widget;

  答案是:没有什么不同。当我们声明template类型参数,class和typename的意义完全相同。

1、从属名称和非从属名称

  但C++并不总是把class和typename视为等价。

  如,用来声明某种类型时。

  假设现在我们有一个模板,其接受一个STL容器类型,然后打印容器中的第二个元素的值,但是这个模板可能会产生错误(甚至不能通过编译)。

template<typename C>
void print2nd(const C& container)
{if (container.size() >= 2) {C::const_iterator iter(container.begin()); //初始化迭代器,绑定到第一个元素上++iter;int value = *iter;std::cout << value;}
}

  这份代码中,我们特别强调两个局部变量:iter和value。这可以引出“嵌套从属名称”和“非从属名称”的概念:

从属名称:对于上面的局部变量iter来说,其是容器container的迭代器类型const_iterator,其实际上取决于template参数C的类型,因此我们称const_iterator为从属名称(意思就是:模板内的一个名称依赖于template的某个参数,那么其就是从属名称)。

  当从属名称属于class内时,我们称其为嵌套从属名称。因此上面的const_iterator就是一种嵌套从属名称。

  非从属名称:再观察value变量,其类型就是int。int是一个并不依赖任何template参数的名称,因此我们称int这样的名称为非从属名称。

  嵌套从属名称有可能导致解析(parsing)困难,举个例子:

  我们修改print2nd模板,假设在其中定义一个迭代器指针:

template<typename C>
void print2nd(const C& container)
{//...//const_iterator可能被编译器理解为C的static成员变量,x为一个变量,下面是两个变量的相乘C::const_iterator* x;//...
}

  好像我们声明x为一个local变量,它是个指针,指向一个C::const_iterator。但之所以我们会这样认为,是因为我们“已经知道”C::const_iterator是个类型?但如果C::const_iterator不是个类型,如果有个static变量恰好被命名为const_iterator,碰巧x是个全局变量呢?因此上面的代码可能会被编译器认为是两个变量在相乘。

  正确的做法是为嵌套从属名称加上一个关键字typename,这样可以显式地告诉编译器某种东西是一个类型,而不是其他东西。代码如下:

template<typename C>
void print2nd(const C& container)
{if (container.size() >= 2) {//使用typename,显式告诉编译器,const_iterator是一个类型typename C::const_iterator iter(container.begin());...}
}

  typename只被用来嵌套从属类型名称;其他名称不该有它存在。例如下面这个函数模板,接受一个容器和一个“指向该容器”的迭代器:

template<typename C>
void f(const C& container,typename C::const_iterator iter);

typename必须作为嵌套从属类型名称的前缀词,这一规则的例外是:

当typename用来声明类型时,其不可以出现在两个地方:

  • ①基类的派生列表内的嵌套从属类型名称之前。

  • ②构造函数的成员初始化列中作为基类的修饰符。

我们假设Nested是基类中的一种类型。例如:

template<typename T>
class Derived :public Base<T>::Nested //此处不允许使用typename
{
public:explicit Derived(int) :Base<T>::Nested(x)//此处不允许使用typename{typename Base<T>::Nested temp; //此处允许使用typename}
};

2、typename在traits机制中的运用

  假设我们正在写一个函数模板,其接受一个迭代器类型,而我们打算在函数中为迭代器所指的对象做一份副本。我们可以这样写:

template<typename IterT>
void workWithIterator(IterT iter)
{typename std::iterator_traits<IterT>::value_type temp(*iter);//...
}

  这里使用的iterator_traits<>模板类,其实一种traits类(参阅条款47)。相当于传递给其一个迭代器类型为其进行实例化,那么我们就可以通过其value_type萃取出迭代器所指的容器的类型。例如:

  • 如果IterT是list::iterator,那么value_type就代表string,temp的类型就是string。

  • 如果IterT是vector::iterator,那么value_type就代表int,temp的类型就是int。

  如果上面的代码比较复杂,那么我们还可以搭配typedef来使用。例如:

template<typename IterT>
void workWithIterator(IterT iter)
{typename std::iterator_traits<IterT>::value_type value_type; //为类型声明别名value_type temp(*iter); //使用类型定义变量//...
}

  关于typename的移植性问题:某些编译器接受typename,而可能有少数编译器不接受typename。因此其存在有移植性。

3、牢记

  • 声明template参数时,前缀关键字class和typename可互换。

  • 请使用关键字typename标识嵌套从属类型名称;但不得在base class lists(基类列)或member initialization list(成员初值列)内以它作为base class修饰符。

总结

期待大家和我交流,留言或者私信,一起学习,一起进步!