何时应该使用vector的reserve()函数?

4

我一直使用resize(),因为我不能使用reserve(),因为它会出现错误:vector subscript out of range。当我阅读关于resize()和reserve()的区别的信息时,我看到像reserve()设置可分配的最大元素数,但resize()是我们目前拥有的。在我的代码中,我知道最大元素数,但reserve()对我没有任何有用的东西。那么,我该如何利用reserve()呢?

4个回答

10
一个向量有一个容量(由capacity()返回)和一个大小(由size()返回)。第一个表示向量可以容纳多少元素,第二个表示它当前容纳了多少元素。 resize改变大小,而reserve仅改变capacity
另请参见resizereserve文档。
至于用例: 假设您预先知道要将多少个元素放入您的vector中,但不想初始化它们-这就是reserve的用例。假设您的向量之前为空;那么,在进行任何insertpush_back操作之前,直接调用reserve(),您当然不能直接访问与您保留空间相同的元素数量,因为您正在尝试访问尚未初始化的元素; size仍然为0。因此,该向量仍然为空;但是,如果您以这种方式选择保留容量,使其大于或等于向量可能达到的最大大小,则可以避免昂贵的重新分配;同时,您还将避免resize会进行(在某些情况下代价高昂的)每个向量元素的初始化。
而对于resize,您可以说:使向量容纳给定大小的元素;将超过旧大小的索引的元素进行初始化,或者删除超出给定新大小的元素。请注意,reserve函数永远不会影响当前向量中的元素(除非需要重新分配空间以更改其存储位置 - 但不会影响元素值或数量!),这意味着如果一个向量的大小当前大于您传递给同一向量的reserve函数的参数,则reserve将不起任何作用。
另请参阅此问题的答案:vector::resize() 和 vector::reserve() 的选择

我知道预先要将多少元素放入向量中,但如果我不操作向量成员,那有什么用呢? - Shibli
“如果我不操作向量的成员”是什么意思?在保留空间的情况下,您需要使用insertpush_back(就像没有进行“reserve”一样),但您将避免重新分配,并且还将避免resize触发的额外初始化...现在清楚了吗? - codeling
是的,但您还需要初始化; 如果您传递给 resize 的内容比向量当前的大小小,则可能还需要删除元素! 请仔细阅读我的答案和文档,其中都有说明。 - codeling
我想我明白了。但是为了确保,如果我使用reserve()然后使用pushback(),因为分配已经完成,pushback()不需要花费时间来分配内存。我是对的吗? - Shibli
有什么我可以在我的答案中添加以使其更清晰吗? - codeling
显示剩余3条评论

3

reserve() 是一个用于 std::vector 的 性能优化

一个典型的 std::vector 实现会在第一次 push_back() 时保留一些内存,例如4个元素。当第5个元素被推入时,向量必须被重新调整大小:新的内存必须被分配(通常是将大小加倍),向量的内容必须被复制到新位置,并删除旧内存。

当向量持有大量元素时,这会成为昂贵的操作。例如当您 push_back() 第2^24+1个元素时,就需要复制1600万个元素才能添加一个元素。

如果您事先知道元素的数量,可以使用 reserve() 预留计划 push_back() 的元素数。在这种情况下,不需要进行昂贵的复制操作,因为内存已经为所需数量预留。

相比之下,resize() 更改vector中的元素数量

如果没有添加任何元素并使用 resize(20),那么现在将可以访问20个元素。分配的内存量也会增加到一个实现相关的值。 如果添加了50个元素并使用resize(20),则最后30个元素将从向量中删除并不再可访问。这不一定会更改分配的内存,但这也可能是与实现相关的。


2

resize(n)会为n个对象分配内存并进行默认初始化。

reserve()会分配内存但不进行初始化。因此,reserve不会改变size()返回的值,但它会改变capacity()的结果。


-2

在 underscore_d 的评论后编辑。

描述了在 VS2015 中如何实现函数。

VS2015 CTP6

此错误对话框仅存在于 DEBUG 模式下,当定义 #if _ITERATOR_DEBUG_LEVEL == 2 时。在 RELEASE 模式下我们没有任何问题。我们通过return (*(this->_Myfirst() + _Pos)获得当前值,因此不需要size值:

    reference operator[](size_type _Pos)
    {   // subscript mutable sequence
    #if _ITERATOR_DEBUG_LEVEL == 2
    if (size() <= _Pos)
        {   // report error
        _DEBUG_ERROR("vector subscript out of range");
        _SCL_SECURE_OUT_OF_RANGE;
        }

    #elif _ITERATOR_DEBUG_LEVEL == 1
    _SCL_SECURE_VALIDATE_RANGE(_Pos < size());
    #endif /* _ITERATOR_DEBUG_LEVEL */

    return (*(this->_Myfirst() + _Pos));
    }

如果我们查看向量的源代码,我们可以发现,resizereserve之间的区别仅在于resize()函数中this->_Mylast()值的更改。 reserve()调用_Reallocateresize()调用_Reserve,该函数调用_Reallocate,然后resize()还会更改this->_Mylast()的值:this->_Mylast() += _Newsize - size();,这在size计算中使用(请参见最后一个函数)。
    void resize(size_type _Newsize)
    {   // determine new length, padding as needed
    if (_Newsize < size())
        _Pop_back_n(size() - _Newsize);
    else if (size() < _Newsize)
        {   // pad as needed
        _Reserve(_Newsize - size());
        _TRY_BEGIN
        _Uninitialized_default_fill_n(this->_Mylast(), _Newsize - size(),
            this->_Getal());
        _CATCH_ALL
        _Tidy();
        _RERAISE;
        _CATCH_END
        this->_Mylast() += _Newsize - size();
        }
    }


    void reserve(size_type _Count)
    {   // determine new minimum length of allocated storage
    if (capacity() < _Count)
        {   // something to do, check and reallocate
        if (max_size() < _Count)
            _Xlen();
        _Reallocate(_Count);
        }
    }



    void _Reallocate(size_type _Count)
    {   // move to array of exactly _Count elements
    pointer _Ptr = this->_Getal().allocate(_Count);

    _TRY_BEGIN
    _Umove(this->_Myfirst(), this->_Mylast(), _Ptr);
    _CATCH_ALL
    this->_Getal().deallocate(_Ptr, _Count);
    _RERAISE;
    _CATCH_END

    size_type _Size = size();
    if (this->_Myfirst() != pointer())
        {   // destroy and deallocate old array
        _Destroy(this->_Myfirst(), this->_Mylast());
        this->_Getal().deallocate(this->_Myfirst(),
            this->_Myend() - this->_Myfirst());
        }

    this->_Orphan_all();
    this->_Myend() = _Ptr + _Count;
    this->_Mylast() = _Ptr + _Size;
    this->_Myfirst() = _Ptr;
    }

    void _Reserve(size_type _Count)
    {   // ensure room for _Count new elements, grow exponentially
    if (_Unused_capacity() < _Count)
        {   // need more room, try to get it
        if (max_size() - size() < _Count)
            _Xlen();
        _Reallocate(_Grow_to(size() + _Count));
        }
    }

    size_type size() const _NOEXCEPT
    {   // return length of sequence
    return (this->_Mylast() - this->_Myfirst());
    }

问题

但是reserve存在一些问题:

  1. end()将等于begin()

23.2.1通用容器要求

5:

end()返回一个迭代器,它是容器的超出末尾值。

    iterator end() _NOEXCEPT
    {   // return iterator for end of mutable sequence
    return (iterator(this->_Mylast(), &this->_Get_data()));
    }

_Mylast() 将等于 _Myfirst()

  1. at() 会生成一个 out_of_range 异常。

23.2.3 序列容器

17:

成员函数 at() 提供了对容器元素的边界检查访问。如果 n >= a.size(),则 at() 抛出 out_of_range 异常。

  1. 在 VisualStudio 调试器中,当大小不为 0 时,我们可以看到向量值

使用 resize

enter image description here

使用reserve和手动设置#define _ITERATOR_DEBUG_LEVEL 0

enter image description here


3
因为UB(未定义行为),所以被downvote了。resize默认构造任何新元素,并允许访问它们的索引。reserve不会初始化新容量,任何尝试在没有对其使用emplacepushresize进行初始化之前访问新分配的元素占位符的操作都是UB。严格的.at()不允许这样做,这应该暗示您不能使用operator[]。详细阐述一个实现中当前发生的事情并不有用,而且可能只会误导读者。 - underscore_d
如果容器大小大于[index] n,则该函数永远不会抛出异常(无异常保证)。否则,行为是未定义的。 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接