【侯捷-SL体系结构内核分析-适配器】
reverse_iterator
inserter_iterator
reverse_iterator
容器除了拥有返回正向迭代器的函数 begin 和 end 之外,还有两个类似的成员函数 rbegin 和 rend,它们返回的是容器的逆向迭代器,它们之间的相互关系如下:
- begin() 返回指向容器头元素的正向迭代器;end() 返回指向容器尾元素的下一个位置的正向迭代器。
- rbegin() 返回指向容器尾元素的逆向迭代器;rend() 返回指向容器头元素的上一个位置的逆向迭代器。
实现原理
逆向迭代器就是正向迭代器的一个 adapter,它的实现原理也很简单,相关源代码如下:
_NODISCARD reverse_iterator rbegin() noexcept
{ // return iterator for beginning of reversed mutable sequence
return (reverse_iterator(end()));
}
_NODISCARD reverse_iterator rend() noexcept
{ // return iterator for end of reversed mutable sequence
return (reverse_iterator(begin()));
}
template<class _BidIt>
class reverse_iterator
{ // wrap iterator to run it backwards
public:
...
_NODISCARD _CONSTEXPR17 reference operator*() const
{ // return designated value
_BidIt _Tmp = current;
return (*--_Tmp);
}
_CONSTEXPR17 reverse_iterator& operator++()
{ // preincrement
--current;
return (*this);
}
protected:
_BidIt current; // the wrapped iterator
};
- 内含一个被适配的正向迭代器。可以看到,该正向迭代器必须为 bidirectional_iterator 类型。
- 对逆向迭代器解引用,取得的是它对应的正向迭代器的上一个元素值,所以对当前临时正向迭代器自减后再解引用。
- 逆向迭代器的自增操作相当于正向迭代器的自减操作。
inserter_iterator
先看下面一段代码:
void main()
{
vector<int> vec1 = { 1, 2 ,3 ,4 ,5 };
//先分配5个数据的内存大小
vector<int> vec2(5);
copy(vec1.begin(), vec1.end(), vec2.begin());
}
当我们利用 copy 函数将源容器的元素拷贝到目标容器时,我们必须先分配好第二个容器的内存足以放下即将被拷贝的数据。
为什么需要先分配好内存呢?可以在回顾看看 copy 关键源代码:
template<class _InIt,
class _OutIt> inline
_OutIt _Copy_unchecked1(_InIt _First, _InIt _Last,
_OutIt _Dest, _General_ptr_iterator_tag)
{ // copy [_First, _Last) to [_Dest, ...), arbitrary iterators
for (; _First != _Last; ++_Dest, (void)++_First)
{
*_Dest = *_First;
}
return (_Dest);
}
可以看到,从源端到目标端采取的直接是一个赋值动作,将源端的值赋值给目标端,所里目标端当然需要事先分配好内存。
再来看看利用 inserter 适配器写出的拷贝操作:
void main()
{
list<int> source, destination;
for (int i = 1; i <= 5; ++i)
{
source.push_back(i);
destination.push_back(i * 10);
}
list<int>::iterator it = destination.begin();
advance(it, 3);
copy(source.begin(), source.end(), inserter(destination, it));
}
将源端的元素拷贝到目标端时,并没有事先在目标端分配内存,而是直接使用 inserter 适配器,将源端元素拷贝插入到目标端中,无需调用者事先分配内存。现在来探探 inserter 内部实现原理。
inserter 的实现源代码如下:
template<class _Container>
_NODISCARD inline insert_iterator<_Container> inserter(_Container& _Cont,
typename _Container::iterator _Where)
{ // return insert_iterator
return (insert_iterator<_Container>(_Cont, _Where));
}
内部返回了一个 inserter_iterator 对象。
template<class _Container>
class insert_iterator
{ // wrap inserts into container as output iterator
public:
...
insert_iterator& operator=(const typename _Container::value_type& _Val)
{ // insert into container and increment stored iterator
iter = container->insert(iter, _Val);
++iter;
return (*this);
}
protected:
_Container * container; // pointer to container
typename _Container::iterator iter; // iterator into container
};
可以看到,inserter_iterator 它内涵目标容器成员,它对赋值操作进行的重载。所以在 copy 函数内部,当将源容器元素赋值给目标容器,也就是下面这句代码:
*_Dest = *_First;
会调用 *_Dest, 也就是 inserter_iterator 的重载 = 函数,在这个函数内部,又会调用 目标容器的 insert 函数,我们知道,容器的 insert 成员函数就是实现自动分配内存和赋值,由此就实现了 copy 函数的自动分配内存和拷贝赋值。
是不是很神奇!!amazing!!
有一个小细节不知道注意到没有。在重载函数内部,当在目标容器内插入元素后,返回的指向插入位置的 iterator 进行了自增动作。但是 copy 函数内部在赋值完后,又做了一次自增动作,这样岂不是往后移动了两个元素位置?
实际上,在 copy 函数内部,是 insert_iterator 这个对象进行了自增操作,而 insert_iterator 已经对自增操作符进行了重载,如下所示,它实际上并没有做任何动作,仅仅返回当前对象。所以,插入时迭代器的顺延动作是由 insert_iterator 类自己负责的。
insert_iterator& operator++()
{ // pretend to preincrement
return (*this);
}