std :: forward_iterator_tag的作用是什么?

时间:2013-07-02 10:12:13

标签: c++ vector standard-library

在对应用程序进行概要分析后,我碰到了gcc 4.7.1附带的标准库实现。它是include/g++-v4/bits/vector.tcc

template<typename _Tp, typename _Alloc>
template<typename _ForwardIterator>
  void
  vector<_Tp, _Alloc>::
    _M_range_insert(iterator __position, _ForwardIterator __first,
          _ForwardIterator __last, std::forward_iterator_tag)
  {
     …
  }

我注意到函数签名的最后一个参数只是一个标记,我开始想知道为什么它在这里。快速查看this page表明std::forward_iterator_tag是一个空结构。它的作用是什么?显然它对函数没用,它可能会浪费一个寄存器或堆栈上的一些空间。那为什么呢?

5 个答案:

答案 0 :(得分:9)

简而言之:标签用于重载,用于优化。

以一个简单的advance为例,您可以设计:

template<class II, class D>
void advance(II& i, D n){
    while( n-- ) ++i;
}

然而,它具有O(n)复杂性,当您拥有random_access_iterator时,这是不可接受的。所以你可以改变你的设计:

template<class II, class D>
void advance_II(II& i, D n){
    while( n-- ) ++i;
}

template<class RAI, class D>
void advance_RAI(RAI& i, D n){
    i += n;
}

template<class II, class D>
void advance(II& i, D n){
    if(is_random_access_iterator(i)) // not yet designed
        advance_RAI(i, n);
    else
        advance_II(i, n);
}

然而,要使用的函数的版本是在运行时决定的,所以我们尝试让编译器决定在编译时选择哪种方法。所以我们给迭代器标签。有五个标签:

struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirection_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirection_iterator_tag {};

现在你可以这样做:

template<class II, class D>
void __advance(II& i, D n, input_iterator_tag){
    while( n-- ) ++i;
}

template<class RAI, class D>
void __advance(RAI& i, D n, random_access_iterator_tag){
    i += n;
}

template<class II, class D>
void advance(II& i, D n){
    __advance(i, n, iterator_traits<II>::iterator_category());
}

答案 1 :(得分:6)

区分不同的_M_range_insert重载。

This reference看起来好一点,并举例说明了如何使用标签结构。

答案 2 :(得分:4)

冒着引用链接的风险..

  

清空类以将迭代器的类别标识为前向迭代器:

它用作标记来标识迭代器的类型,因此这里的函数_M_range_insert可以正常运行。由于它是类型名称,因此可用于触发不同的重载。

在我的impl中,我有

void _Insert(const_iterator _Where, _Iter _First, _Iter _Last,
            _Int_iterator_tag)

void _Insert(const_iterator _Where, _Iter _First, _Iter _Last,
            input_iterator_tag)

void _Insert(const_iterator _Where, _Iter _First, _Iter _Last, 
            forward_iterator_tag)

其他重载。

答案 3 :(得分:1)

它是模板元编程机制的一部分,它用于根据参数的特征选择适当的重载,因此,例如,如果您有随机访问迭代器,您可以利用它并检查它们之间的距离并在插入之前保留。另一方面,如果你只有前向迭代器检查距离将是O(n),所以你不要这样做,只是向后推,这可能导致多次重定位,因此速度较慢。 编译器也会优化这些空结构,因此没有运行时惩罚。

答案 4 :(得分:0)

有一些重载用于插入向量,其中一些重载用于构造函数。如果向量元素是整数类型,则其中两个重载会发生冲突:

iterator insert( const_iterator pos, size_type count, const T& value );

template< class InputIt >
iterator insert( const_iterator pos, InputIt first, InputIt last );

如果您有vector<int>并致电vec.insert(vec.bgein(), 5, 4),那么您肯定要插入价值4的5倍。但是重载解析会看到模板并调用该模板,推导InputIt为是int

为了解决这个问题和其他一些行为,标准库的实现者发明了一些特征和一堆标记类。这些特征是一个模板元函数,它会给出三个不同的标签,比如Karthik T在他的回答中说:

  • _Int_iterator_tag如果InputIt是整数类型
  • forward_iterator_tag如果InputIt是前向迭代器(包括随机访问迭代器)
  • input_iterator_tag如果InputIt是不是前向迭代器的迭代器类型

然后你会有一堆_M_range_insert的重载,将不同的标签类型作为附加参数,每个都做正确的事,意味着

  • Int-Tag的重载将重定向到insert(或其实现)的第一个非模板化重载
  • forward-iterators的重载调用reserve(std::distance(first,last))并在此之后从迭代器范围复制元素
  • 普通输入迭代器的重载只是复制元素,可能导致多次重新分配。它不能调用reserve,因为输入迭代器只能被计算一次(例如istream迭代器的东西)

然后,模板化的插入方法在概念上看起来像这样:

template< class InputIt >
iterator insert( const_iterator pos, InputIt first, InputIt last )
{
  return _M_range_insert(pos, first, last, InsertIteratorTraits<InputIt>::tag_type());
}