code>需要新的容量为6。
  • 优化将容量扩大到正好6,然后通过从 src 迭代器( beg , end )。

  • 此复制在未进行容量检查的循环内完成。 (这是优化。)

  • 在复制过程中,在 do_evil
  • 中向向量中添加更多元素(无效迭代器) code>。

    也许你不得不使用<$ c在使用 do_evil 之前,在示例中显式强制更新observable capacity 。目前, insert 可以保留一些容量,但是只有在复制完成后才改变容量返回(即可观察的容量)。






    我在标准中发现的内容似乎允许优化 insert :



    [sequence.reqmts] / 3


    解决方案

    第17.6.4.9节)是对您尝试执行的操作的更清楚的禁止:

    我不知道这个行为是什么意思



    此外, push_back()保证(23.3.7.5):

    您的位置传递给 insert ,插入调用之前评估的 dst.end()在第一个 evil_feedback-> push_back()调用的插入点之前,因此它不会保持有效(事实上,您在此谨慎避免重新分配保存你,因为你只遇到一半的条件)。这意味着您传递给 std :: vector :: insert (在C ++标准库中定义的函数)的参数在调用期间无效,未定义行为的范围。



    上一个回答:


    $ b 我认为您违反了您引用的前提条件:


    vector::insert(dst_iterator, src_begin, src_end) (insert a range) can be optimized for random-access iterators to reserve the required capacity src_end - src_begin first, then perform the copy.

    The main question I have: Does the Standard also allow vector::insert to avoid a capacity check for each copied element? (I.e. not using push_back or similar on every element to be inserted)

    I'll refer to avoiding this capacity check as "optimization of insert".


    What could go wrong: I can imagine an iterator with side effects when dereferenced:

    Note: the Standard guarantees the iterators passed to insert will be dereferenced exactly once (see end of question).

    #include <vector>
    #include <iterator>
    #include <iostream>
    
    template < typename T >
    struct evil_iterator : std::iterator < std::random_access_iterator_tag, T >
    {
        using base = std::iterator < std::random_access_iterator_tag, T >;
    
        std::vector<T>* evil_feedback;
        typename std::vector<T>::iterator innocent_iterator;
    
        evil_iterator( std::vector<T>* c,
                       typename std::vector<T>::iterator i )
            : evil_feedback{c}
            , innocent_iterator{i}
        {}
    
        void do_evil()
        {
            std::cout << "trying to do evil; ";
            std::cout << "cap: " << evil_feedback->capacity() << ", ";
            std::cout << "size: " << evil_feedback->size() << ", ";
    
            // better not invalidate the iterators of `*evil_feedback`
            // passed to the `insert` call (see example below)
            if( evil_feedback->capacity() > evil_feedback->size() )
            {
                evil_feedback->push_back( T{} );
                // capacity() might be == size() now
                std::cout << "successful >:]" << std::endl;
            }else
            {
                std::cout << "failed >:[" << std::endl;
            }
        }
    
        T& operator*()
        {
            do_evil();  // <----------------------------------------
            return *innocent_iterator;
        }
    
    
        // non-evil iterator member functions-----------------------
    
        evil_iterator& operator++()
        {
            ++innocent_iterator;
            return *this;
        }
        evil_iterator& operator++(int)
        {
            evil_iterator temp(*this);
            ++(*this);
            return temp;
        }
    
    
        evil_iterator& operator+=(typename base::difference_type p)
        {
            innocent_iterator += p;
            return *this;
        }
        evil_iterator& operator-=(typename base::difference_type p)
        {
            innocent_iterator -= p;
            return *this;
        }
    
        evil_iterator& operator=(evil_iterator const& other)
        {
            evil_feedback = other.evil_feedback;
            innocent_iterator = other.innocent_iterator;
            return *this;
        }
    
        evil_iterator operator+(typename base::difference_type p)
        {
            evil_iterator temp(*this);
            temp += p;
            return temp;
        }
        evil_iterator operator-(typename base::difference_type p)
        {
            evil_iterator temp(*this);
            temp -= p;
            return temp;
        }
    
        typename base::difference_type operator-(evil_iterator const& p)
        {
            return this->innocent_iterator - p.innocent_iterator;
        }
    
        bool operator!=(evil_iterator const& other) const
        {  return innocent_iterator != other.innocent_iterator;  }
    };
    

    Example:

    int main()
    {
        std::vector<int> src = {3, 4, 5, 6};
        std::vector<int> dst = {1, 2};
    
        evil_iterator<int> beg = {&dst, src.begin()};
        evil_iterator<int> end = {&dst, src.end()};
    
        // explicit call to reserve, see below
        dst.reserve( dst.size() + src.size() );
        // using dst.end()-1, which stays valid during `push_back`,
        //   thanks to Ben Voigt pointing this out
        dst.insert(dst.end()-1, beg, end);  // <--------------- doing evil?
    
        std::copy(dst.begin(), dst.end(), 
                  std::ostream_iterator<int>{std::cout, ", "});
    }
    


    Questions:

    1. Can vector::insert be optimized to avoid a capacity check for each inserted element?
    2. Is evil_iterator still a valid iterator?
    3. If so, is evil_iterator evil, i.e. can it result in UB / non-complying behaviour if insert is optimized as described above?

    Edit 2: Added the call to reserve. Now, I'm doing evil :)

    Edit: Why I think the optimization could break this:

    1. Consider dst.size() == dst.capacity() == 2 at the beginning.
    2. The call to insert requires a new capacity of 6.
    3. The optimization enlarges the capacity to exactly 6, then begins to insert the elements by copying from the src iterators (beg, end).
    4. This copying is done within a loop where no capacity checks occur. (That is the optimization.)
    5. During the process of copying, further elements are added to the vector (w/o invalidating the iterators), in do_evil. The capacity now is not sufficient any more to hold the rest of the elements to be copied.

    Maybe you had to use reserve in the example explicitly to force updating the observable capacity before using do_evil. Currently, insert could reserve some capacity but change what capacity returns (i.e. observable capacity) only after the copying is done.


    What I've found in the Standard so far seems to allow the optimization of insert:

    [sequence.reqmts]/3

    [vector.modifiers] on insert

    解决方案

    Looking again, I think this rule (section 17.6.4.9) is a clearer prohibition on what you tried to do:

    I think this rule applies during the entire duration of the function call, and not only at function entry.

    Furthermore, push_back() guarantees that (23.3.7.5):

    Your position passed to insert, which is dst.end() as evaluated before the insert call, is not before the insertion point of the first evil_feedback->push_back() call, so it does not remain valid (the fact that you carefully avoided reallocation here does not save you, as you only met half the condition). Which means the argument you passed to std::vector::insert, a function defined in the C++ Standard Library, is invalid during the duration of that call, landing you squarely in the realm of undefined behavior.


    Previous answer:

    I think you violated this precondition that you quoted:

    这篇关于是否允许向量::插入只保留一次,并避免进一步的容量检查?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

    10-25 05:19