Какова роль тега std:: forward iterator?
при профилировании приложения я столкнулся с этой частью стандартной реализации библиотеки, поставляемой с 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)
{
…
}
Я заметил, что последний аргумент функции подписи-это просто тег, и я начал задаваться вопросом, почему он был здесь. Быстрый взгляд на на этой странице видно, что std::forward_iterator_tag
- это пустая структура. Какова его роль здесь? Очевидно, что это бесполезно для функции, и это может потратить регистр или некоторое пространство в стеке. Так почему?
5 ответов
в двух словах: теги для перегрузки, для оптимизации.
возьмем простой 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());
}
чтобы различать разные _M_range_insert
перегрузок.
этой ссылке кажется немного лучше и имеет пример того, как можно использовать структуры тегов.
С риском цитирования вашей ссылки..
пустой класс для определения категории итератора как прямого итератора:
Он используется в качестве тега для определения типа итератора, так что функции здесь _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)
среди прочих перегрузок.
Это часть механизма метапрограммирования шаблонов, и он используется для выбора соответствующей перегрузки на основе признаков аргументов, поэтому, например, если у вас есть итераторы произвольного доступа, вы можете воспользоваться этим и проверить расстояние между ними и зарезервировать перед вставкой. С другой стороны, если у вас есть только прямые итераторы, проверка расстояния будет O(n), поэтому вы этого не делаете, просто нажмите назад, что может привести к нескольким перемещениям и, следовательно, будет медленнее. Также компилятор оптимизирует out эти пустые структуры, поэтому нет штрафа во время выполнения.
существует несколько перегрузок для вставки в вектор, некоторые из них дублируются для конструкторов. Две из этих перегрузок имеют конфликт, если векторные элементы имеют интегральный тип:
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)
, вы, конечно, хотите вставить 5 раз значение 4. Но разрешение перегрузки увидит шаблон и вызовет его, выводя InputIt
на int
.
чтобы решить эту проблему и некоторые другие поведенческие вещи, разработчики стандартной библиотеки изобрели некоторые черты и кучу классов тегов. Черты-это метафункция шаблона, которая даст три разных тега, как сказал Картхик т в своем ответе:
-
_Int_iterator_tag
еслиInputIt
имеет интегральный тип -
forward_iterator_tag
еслиInputIt
является прямым итератором (который включает итераторы произвольного доступа) -
input_iterator_tag
еслиInputIt
- это тип итератора, который не является прямым итератором
вы тогда будет куча перегрузок _M_range_insert
, принимая различные типы тегов в качестве дополнительных параметров, каждый делает правильную вещь, что означает
- перегрузка для Int-тега будет перенаправлена на первую не шаблонную перегрузку
insert
(или ее реализации) - перегрузка для прямых итераторов вызывает
reserve(std::distance(first,last))
и копирует элементы из диапазона итератора после этого - перегрузка для простых входных итераторов просто копирует элементы, возможно, вызывающие множественные перераспределения. Он не может вызвать
reserve
, потому что входные итераторы могут быть оценены только один раз (например, итераторы istream)
метод templated insert будет выглядеть концептуально следующим образом:
template< class InputIt >
iterator insert( const_iterator pos, InputIt first, InputIt last )
{
return _M_range_insert(pos, first, last, InsertIteratorTraits<InputIt>::tag_type());
}