从std :: copy和std :: copy_n中提取输入迭代器

时间:2018-02-02 10:21:17

标签: c++ stl iterator

我尝试实现一个取反序列化方法,该方法接受输入迭代器并执行一系列块读取(使用std::copystd::copy_n)。像这样的东西(只是一个例子):

template <class InputIt>
InputIt unserialize(InputIt it)
{
  std::copy_n(it, sizeof(header_type), reinterpret_cast<char*>(&header));
  std::copy_n(it, header.payload_size, std::back_inserter(payload));
  it = optional.unserialize(it);
  return it;
}

在这种情况下如何推进输入迭代器,以便每次跟随std::copy_n的调用继续读取它并最终返回它?

出于性能原因,我希望对迭代器类别(尤其是RandomAccessIterator和InputIterator)具有通用性,并希望可以使用std::copy方法而无需重写这些方法。像绑定检查这样的东西将由迭代器适配器完成,或者如果已知大小,则在反序列化调用之前检查。

什么不起作用也不可接受:

  1. 使用std::copy_n<InputIt&>(it, ...)可能适用于某些类别但不适用于所有类别,而且它太不可靠。
  2. 每次调用后使用std::advance会导致重新读取一些迭代器的同一个块。不是优选的,对某些来源可能是不可能的。
  3. 更新制作迭代器引用适配器没有帮助,因为随机访问迭代器版本的copy_n返回指向过去的指针输入迭代器版本时复制的元素返回指向复制的最后一个元素的指针。所以我想自己的copy_n版本最适合使用额外的迭代器适配器进行绑定检查。

3 个答案:

答案 0 :(得分:2)

对于random access iterator,可以使用此表格 - 这很好:

template <class InputIt, class N, class OutputIt>
InputIt copy_n_advance_input(InputIt it, N dist, OutputIt outIt)
{
    std::copy_n(it, dist, outIt);
    return std::next(it, dist);
}

不幸的是 - 问题在于我们想要处理一个通道输入迭代器 - 就像这里(得到'd' - 不是'c'):

std::string s = "abcd";
std::istringstream ss{s};
auto e = copy_n_advance_input(std::istream_iterator<char>(ss), 
                             2, 
                             std::ostream_iterator<char>(std::cout, ","));
std::cout << "\n" << *e << "\n";

因此,似乎需要像STL一样需要两种形式:

template <class InputIt, class N, class OutputIt>
InputIt copy_n_advance_input_impl(InputIt it, N dist, OutputIt outIt,
                                  std::input_iterator_tag)
{
    while (dist-- > 0)
    {
        *outIt = *it;
        ++outIt;
        ++it;
    }
    return it;
}

template <class InputIt, class N, class OutputIt>
InputIt copy_n_advance_input_impl(InputIt it, N dist, OutputIt outIt, 
                                  std::random_access_iterator_tag)
{
    std::copy_n(it, dist, outIt);
    return std::next(it, dist);
}

template <class InputIt, class N, class OutputIt>
InputIt copy_n_advance_input(InputIt it, N dist, OutputIt outIt)
{
    return copy_n_advance_input_impl(it, dist, outIt, typename std::iterator_traits<InputIt>::iterator_category {});
}

注意,std::input_iterator_tag的建议版本不如STL(至少对于gcc)有效 - 它为输入做了额外的迭代 - 这次迭代不是执行复制所必需的 - 但它需要返回“复制后”范围的开头(stl_algo.h):

754   template<typename _InputIterator, typename _Size, typename _OutputIterator>
755     _OutputIterator
756     __copy_n(_InputIterator __first, _Size __n,
757          _OutputIterator __result, input_iterator_tag)
758     {
759       if (__n > 0)
760     {
761       while (true)
762         {
763           *__result = *__first;
764           ++__result;
765           if (--__n > 0)
766         ++__first;
767           else
768         break;
769         }
770     }
771       return __result;
772     }

最后注意 - 对于随机访问迭代器(如std :: vector :: iterator),使用std算法的版本更为明智 - 因为它们可以更加优化 - 例如对于POD类型的连续内存迭代器 - 可以只是memcpy'ied。或者存在std::vector<bool>std::deque<T>的一些专门化,它们使用其内部结构以最有效的方式执行复制。

答案 1 :(得分:1)

您可以创建迭代器适配器,它始终通过引用工作到提供的迭代器。这是这种适配器的基本框架:

#include <iterator>

template<typename InputIterator>
struct IteratorReference
{
    InputIterator& it;

    IteratorReference(InputIterator& it)
        : it(it)
    {}
    // {copy, move, destructor} == default

    // iterator methods (TODO: add the rest)
    IteratorReference& operator++()
    { ++it; return this; }

    typename InputIterator::reference operator*()
    { return *it; }

    // Convert back to original iterator
    operator InputIterator() const
    { return it; }
};


template<typename InputIterator>
IteratorReference<InputIterator> make_iterator_reference(InputIterator it)
{
    return it;
}

要使用它,只需创建并使用包装器代替原始迭代器:

#include <algorithm>
#include <vector>

struct Header
{
    std::size_t payload_size;
};

template <class InputIt>
InputIt unserialize(InputIt it, Header& header, std::vector<char>& payload)
{
    auto i_ref = make_iterator_reference(it);
    std::copy_n(i_ref, sizeof header, reinterpret_cast<char*>(&header));
    std::copy_n(i_ref, header.payload_size, std::back_inserter(payload));
    i_ref = optional.unserialize(i_ref);
    return i_ref;
}

答案 2 :(得分:0)

我知道这是一个老问题,但...... 最近我遇到了类似的问题,当我发现这个问题没有任何好的解决方案时,我感到非常难过...... 尽管如此,我已经尝试编写一个像Toby Speight所说的迭代器包装器,并且我已经扩展了一点。 此包装器仅用于InputIterator,因为std::copystd::copy_n支持的其他类型的迭代器是多通迭代器,并且可以直接使用std::next和{{1}而不是使用这个包装器。

std::advance

要使用它们,首先需要一个保存迭代器的template<typename Iterator> struct Wrapper { using traits = std::iterator_traits<Iterator>; using difference_type = typename Traits::difference_type; using reference = typename Traits::reference; //... reference operator*() const { return *(*(this->current)); } //need implement operator++(int) too, in the similar way Wrapper& operator++() { if(this->indicator != 0) { ++(*(this->current)); --this->indicator; } return *this; } //need to implement operator!= too, in the similar way bool operator==(const Wrapper& other) const { return *(this->current) == *(other.current) or this->indicator == other.indicator; } Iterator* current; difference_type indicator; }; template<typename Iterator> struct Range { using category = typename std::iterator_traits<Iterator>::iterator_category; //... using difference_type = typename std::iterator_traits<Iterator>::difference_type; constexpr bool isForwardOrAbove() { return std::is_base_of_v<std::forward_iterator_tag, category>; } using iterator = std::conditional_t<isForwardOrAbove(), Iterator, Wrapper<Iterator>>; std::pair<iterator, iterator> splitSubRange(difference_type n) { if constexpr (isForwardOrAbove()) { //forward iterators are multi-pass iterators, so using std::advance on one iterator will not invalidate its copies auto oldBegin = this->begin; std::advance(this->begin, n); return {oldBegin, std::next(oldBegin, n)}; } else { //non-forward-iterator return {{&(this->begin), n}, {&(this->end), 0}}; } } Iterator begin; Iterator end; } 对象,并使用Range方法获取普通迭代器,或者,如果它们只是splitSubRange s,则使用迭代器包装器

InputIterator