vector :: insert是否只允许保留一次并避免进一步的容量检查?

时间:2013-05-17 19:08:39

标签: c++ vector iterator language-lawyer

可以针对随机访问迭代器优化

vector::insert(dst_iterator, src_begin, src_end)(插入范围)以首先保留所需的容量src_end - src_begin,然后执行复制。

主要问题我有:标准是否也允许vector::insert避免对每个复制元素进行容量检查? (即,不要在每个要插入的元素上使用push_back或类似物)

我将避免将此容量检查视为“insert”的优化。


可能出现的问题:我可以想象一个迭代器,当被解除引用时会出现副作用

注意:标准保证传递给insert的迭代器将被解除引用一次(参见问题结尾)。

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

示例:

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, ", "});
}

问题:

  1. 可以优化vector::insert以避免对每个插入的元素进行容量检查吗?
  2. evil_iterator仍然是有效的迭代器吗?
  3. 如果是,evil_iterator evil ,即如果insert如上所述进行了优化,是否会导致UB /不符合行为?
  4. 也许我的do_evil不够邪恶..在clang ++ 3.2上没有问题(使用libstdc ++):

    编辑2:添加了对reserve的调用。现在,我在做恶:)

      

    试图做恶;上限:6,大小:2,成功&gt;:]
      试图做恶;上限:6,大小:3,成功&gt;:]
      试图做恶;上限:6,大小:4,成功&gt;:]
      试图做恶;上限:6,大小:9,失败&gt;:[
      1,3,4,5,6,0,0,135097,2,

    编辑:为什么我认为优化可以打破这个:

    1. 在开头考虑dst.size() == dst.capacity() == 2
    2. insert的调用需要新容量为6。
    3. 优化将容量精确地扩大到6,然后通过从src迭代器(begend)复制来开始插入元素。
    4. 此复制在没有进行容量检查的循环内完成。 (这就是优化。)
    5. 在复制过程中,在do_evil中将更多元素添加到向量中(没有使迭代器无效)。现在的容量不足以容纳要复制的其余元素。
    6. 在使用reserve之前,您可能必须明确使用示例中的capacity来强制更新可观察的do_evil。目前,insert可以保留一些容量,但只有在复制完成后才会更改capacity返回的内容(即可观察容量)。


      到目前为止,我在标准中找到的内容似乎可以优化insert

      [sequence.reqmts] / 3

        

      a.insert(p,i,j) [...]

           

      要求:T应该是来自* i的EmplaceConstructible到X.

           

      对于vector,如果迭代器不满足前向迭代器要求(24.2.5),T也应该是MoveInsertable到X和MoveAssignable。范围[i,j)中的每个迭代器只需解除引用一次。

           

      pre:i和j不是迭代器。在p

      之前插入[i,j]中的元素副本
      {p> [vector.modifiers] insert

        

      1备注:如果新大小大于旧容量,则会导致重新分配。如果没有重新分配,插入点之前的所有迭代器和引用仍然有效。如果除了复制构造函数之外抛出异常,移动构造函数,赋值运算符或T的移动赋值运算符,或者通过任何InputIterator操作都没有效果。如果非CopyInsertable T的移动构造函数抛出异常,则效果未指定。

           

      2复杂性:插入元素的数量加上到向量末尾的距离是复杂的。

3 个答案:

答案 0 :(得分:3)

再看一遍,我认为这条规则(第17.6.4.9节)更清楚地禁止你试图做的事情:

  

以下各项适用于C ++标准库中定义的函数的所有参数,除非另有明确说明。

     
      
  • 如果函数的参数具有无效值(例如函数域外的值或指针对其预期用途无效),则行为未定义。
  •   

我认为此规则适用于函数调用的整个持续时间,而不仅仅适用于函数入口。

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

  

如果没有重新分配,插入点之前的所有迭代器和引用仍然有效。

position传递给insert dst.end()insert之前评估的evil_feedback->push_back()不是的插入点之前的第一个std::vector::insert调用,所以它不会保持有效(这里你小心避免重新分配的事实并没有拯救你,因为你只满足了一半的条件)。这意味着您传递给i的参数(在C ++标准库中定义的函数)在该调用期间无效,使您完全处于未定义行为的范围内。


上一个回答:

我认为你违反了你引用的前提条件:

  

pre:ja不是{{1}}的迭代器。

答案 1 :(得分:1)

(注意:这更多是评论,我使用的答案是允许格式化和更长的内容。标记CW因为评论不应该接收代表)

我相信这是一个正确的算法,如果输入迭代器是随机访问的话,它可以避免O(NM)的复杂性:

  1. 确定要插入的范围的大小(仅适用于随机访问迭代器)。
  2. 预留额外空间。
  3. 调整大小。
  4. 移动构造新的尾部元素。
  5. 移动 - 将其他干预元素分配给新的一端。
  6. 将源元素复制到移动中留空的范围内。

答案 2 :(得分:0)

以下是我的观点:

  1. 是;取消引用可能会对您的向量产生副作用(例如,在某些情况下会导致未定义的行为),但这不应该是标准符合迭代器的情况。
  2. 否; Iterator用作指针的泛化 - 因为指针解引用可能没有副作用(找不到引用),迭代器[iterator.requirements.general]也应如此。鉴于这种解释,“插入优化”(1)是有效的。