Почему реализация библиотеки Microsoft Visual C ++ «разворачивает» итераторы? - PullRequest
1 голос
/ 26 апреля 2020

На протяжении реализации microsofts stl , почти все их итераторы развернуты перед использованием.

Например, for_each выглядит например:

template <class _InIt, class _Fn>
_Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    for (; _UFirst != _ULast; ++_UFirst) {
        _Func(*_UFirst);
    }
    return _Func; }

_Adl_verify_range проверяет, что first <= last, что я понимаю, но я не совсем понимаю цель _Get_unwrapped ():

#if _HAS_IF_CONSTEXPR
template <class _Iter>
_NODISCARD constexpr decltype(auto) _Get_unwrapped(_Iter&& _It) {
    // unwrap an iterator previously subjected to _Adl_verify_range or otherwise validated
    if constexpr (is_pointer_v<decay_t<_Iter>>) { // special-case pointers and arrays
        return _It + 0;
    } else if constexpr (_Unwrappable_v<_Iter>) {
        return static_cast<_Iter&&>(_It)._Unwrapped();
    } else {
        return static_cast<_Iter&&>(_It);
    }
}
#else // ^^^ _HAS_IF_CONSTEXPR / !_HAS_IF_CONSTEXPR vvv
template <class _Iter, enable_if_t<_Unwrappable_v<_Iter>, int> = 0>
_NODISCARD constexpr decltype(auto) _Get_unwrapped(_Iter&& _It) {
    // unwrap an iterator previously subjected to _Adl_verify_range or otherwise validated
    return static_cast<_Iter&&>(_It)._Unwrapped();
}

Кажется, что он хочет распадаем итератор или приводим его к rvalue-ссылке.

Итак, мой вопрос: почему Visual ++ использует эту парадигму? G CC не делает этого, насколько я могу судить.

EDIT

По запросу, источник iterator._Unwrapped ()

_NODISCARD constexpr _Ptr _Unwrapped() const noexcept {
    return _Myptr;
}

_Myptr определен в самом итераторе и является просто необработанным указателем:

template <class _Ptr>
class unchecked_array_iterator {
...
private:
    _Ptr _Myptr; // underlying pointer
}

Ответы [ 2 ]

2 голосов
/ 26 апреля 2020

Почему VC ++ переносит итераторы?

Это выбор дизайна. Действительно, для массивоподобных типов, таких как std::array и std::vector, итератор может быть простым от typedef до T*, который прекрасно удовлетворяет семантике итератора, и, действительно, именно так GNU stdlibc ++ реализует это. Но со стандартной точки зрения iterator является подобным указателю объектом, но не обязательно указателем. Итак ...

  1. Предполагать, что указатель будет ошибкой и может привести к непереносимому коду ( в данном случае ).
  2. Обтекание итераторов позволяет выполнять отладку итератора (см. _ITERATOR_DEBUG_LEVEL). Например, вот отладка operator++:

    _CONSTEXPR17 _Array_const_iterator& operator++() {
        _STL_VERIFY(_Ptr, "cannot increment value-initialized array iterator");
        _STL_VERIFY(_Idx < _Size, "cannot increment array iterator past end");
        ++_Idx;
        return *this;
    }
    

Почему VC ++ разворачивает итераторы?

  1. Это оптимизация сообщений об ошибках. В таких циклах, как std::for_each, вместо получения ошибки диапазона в середине for_each об ошибке сообщается при входе в for_each.

  2. Это оптимизация производительности.

    • В режиме отладки проверка предварительных условий один раз приводит к более быстрому коду (режим отладки уже в 10+ раз медленнее, поэтому производительность по-прежнему имеет значение даже в режиме отладки).
    • В режиме выпуска Компилятор может оптимизировать доступ к указателю лучше, чем выводить его через итератор каждый раз. Да, в большинстве случаев он все еще может выводить его, но дополнительный уровень косвенности может привести к другому принятию решений в отношении вставки в другом месте кода.
0 голосов
/ 26 апреля 2020

Если я правильно понимаю, это функция отладки.

Обернутые итераторы содержат дополнительную информацию, которая позволяет реализации проверять различные предварительные условия, например, формируют ли два итератора допустимый диапазон, которого у итератора нет было признано недействительным, что итератор используется только с контейнером, к элементу которого он относится. _Adl_verify_range является одной из таких проверок.

Но тогда, когда итератор фактически используется в алгоритме, реализация не хочет нести накладные расходы на проверки (что будет происходить для каждой операции ++) , Он разворачивает итератор до версии, которая не имеет функций безопасности. Обычно это означает, что развернутая версия - это просто указатель (не обязательно пустой, но обернутый в класс).

В сборках Release сам алгоритм может быть хорошо оптимизирован, так как задействованы только очень простые типы и никаких проверок, но первоначальные проверки безопасности могут остаться или могут быть опущены, как пожелает разработчик.

...