在对应用程序进行概要分析后,我碰到了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
是一个空结构。它的作用是什么?显然它对函数没用,它可能会浪费一个寄存器或堆栈上的一些空间。那为什么呢?
答案 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
的重载,将不同的标签类型作为附加参数,每个都做正确的事,意味着
insert
(或其实现)的第一个非模板化重载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());
}