【问题标题】:Why does theMicrosoft Visual C++ library implementation 'unwrap' iterators?为什么 Microsoft Visual C++ 库实现“解包”迭代器?
【发布时间】:2020-04-26 11:37:22
【问题描述】:

在整个microsofts stl implementation 中,几乎所有的迭代器在使用之前都解包

例如,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 &lt;= 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();
}

它似乎想要衰减迭代器,或者将其转换为右值引用。

所以我的问题是为什么 Visual++ 使用这种范例?据我所知,GCC 没有这样做。

编辑

根据要求,iterator._Unwrapped()的来源

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

_Myptr 在迭代器本身中定义,只是一个原始指针:

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

【问题讨论】:

  • 什么是_Unwrapped()?我猜 MS 迭代器有一些与调试相关的包装器。
  • 这不会引入开销 -- 我看到constexpr,那么你指的是什么开销?
  • @PaulMcKenzie 后一个版本不是 constexpr,如果它不可用的话。即使使用 constexpr 也会有更多的编译时间。也许我应该对我的措辞更加谨慎。我已经更新了这个问题,我主要只是对它为什么这样做感兴趣。
  • @IgorR。我放弃了这个,因为它有点像兔子洞,而且我没有足够的经验来说清楚。但是,是的,这是内置在迭代器中的,它似乎只是调用了 _Unfancy(),它返回了相应的指针。
  • @rustyx 我已经包含了它。

标签: c++ visual-c++ stl


【解决方案1】:

为什么 VC++ 包装迭代器?

这是一种设计选择。实际上,对于像std::arraystd::vector 这样的类数组类型,迭代器可以是简单的typedefT*,它很好地满足了迭代器的语义,这就是GNU stdlibc++ 实现它的方式。但从标准的角度来看,iterator 是一个 pointer-like 对象,但不一定是指针。所以……

  1. 假设它是一个指针将是一个错误,并可能导致不可移植的代码 (case in point)。
  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 倍以上,因此即使在调试模式下性能仍然很重要)。
    • 在发布模式下,编译器可以优化指针访问,而不是每次都通过迭代器推断它。是的,在大多数情况下它仍然可以推断出它,但是额外的间接级别可能会导致关于代码中其他地方的内联的不同决策。

【讨论】:

  • 如果是这样的话,他们一开始就费心制作这些迭代器包装器似乎很奇怪。 GCC 只是使用 T* 作为 stl 数组的迭代器,而 Visual++ 有一个自定义的数组迭代器类。所以显然他们构建了这个迭代器包装器,然后在每个要使用的函数中解包它。看起来很奇怪 - 除非我当然错过了它确实有用的所有情况。
  • 在 Debug 构建迭代器中进行范围检查。如果迭代器是 typedef 的指针,那就不好了,这可能导致代码不可移植。因此,展开是一个实现细节,意在对用户隐藏。
【解决方案2】:

如果我理解正确,这是一个调试功能。

包装的迭代器包含额外的信息,允许实现验证各种先决条件,例如两个迭代器是否形成一个有效范围,一个迭代器没有被无效,一个迭代器只与它所引用的元素的容器一起使用. _Adl_verify_range 就是这样一种检查。

但是,当迭代器实际用于算法时,实现不希望产生验证开销(每个++ 操作都会发生这种情况)。它将迭代器解包到不具有安全功能的版本。通常这意味着,未包装的版本只是一个指针(不一定是裸指针,而是包装在一个类中)。

在 Release 构建中,算法本身可以得到很好的优化,因为只涉及非常简单的类型并且没有检查,但初始安全检查可以保留或可以省略,如开发人员所愿。

【讨论】:

    猜你喜欢
    • 2011-09-28
    • 2015-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-19
    相关资源
    最近更新 更多