簡體   English   中英

從std :: copy和std :: copy_n中提取輸入迭代器

[英]Extract input iterator from std::copy and std::copy_n

我試圖實現一個unserialization方法,它接受一個輸入迭代器並執行一系列塊讀取(使用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會導致為某些迭代器重新讀取相同的塊。 不是優選的,對某些來源可能是不可能的。

UPDATE使迭代器參考適配器隨機訪問迭代器版本並不能幫助copy_n返回指向過去 ,而輸入迭代版本返回指針復制到復制的最后一個元素的最后一個元素。 所以我猜自己的copy_n版本最適合使用額外的迭代器適配器進行綁定檢查。

對於隨機訪問迭代器,可以使用此表單 - 並且它很好:

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)有效 - 它為輸入做了額外的迭代 - 這個迭代不是執行復制所必需的 - 但它需要返回“之后”復制“range(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> ,它們使用它們的內部結構以最有效的方式執行復制。

您可以創建一個迭代器適配器 ,它始終通過對提供的迭代器的引用來工作。 這是這種適配器的基本框架:

#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;
}

我知道這是一個老問題,但是......最近我遇到了類似的問題,當我看到這個問題沒有任何好的解決方案時我感到非常難過......不過,我試過寫像Toby Speight所說的迭代器包裝器,我已經擴展了一點。 這個包裝器只用於InputIterator ,因為std::copystd::copy_n支持的其他類型的迭代器是多遍迭代器,並且可以直接在它們上使用std::nextstd::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方法獲取普通迭代器,或者,如果它們只是InputIterator ,則使用迭代器包裝器。

template<typename InputIterator>
InputIterator unserialize(InputIterator begin, InputIterator end) {
    auto range = Range<InputIterator>{begin, end};
    auto [begin, end] = range.splitSubRange(sizeof(header_type));
    //before having "used" the iterator obtained from range, you shouldn't use range again.
    std::copy(begin, end, header.begin());
    //after having "used" the iterator, you can use the range object to obtain the next sub-range
    auto [begin2, dummy] = range.splitSubRange(sizeof(header_type_2));
    std::copy_n(begin2, sizeof(header_type_2), header2.begin());
    ...
    return range.begin;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM