vector的性能利器:reserve

std::vector使用reserve提高性能

转载请注明文章出处:https://tlanyan.me/reserve-of-vector/

vectorC++编程时的常用容器,其帮助用户自动管理存储空间,简单易用,且能避免资源泄露的问题。需要动态分配存储空间的场景,完全可替代原生数组。

vector被人诟病的地方在于性能。C++ 11引入array容器,有原生数组的性能,编译期能确定大小的情况可取代vector。但对于运行期才能确定大小的情况,array不适用,还是得上vector

实践中提高vector性能的要点是尽量使用reserve(仅次于换编译器和STL实现)。运行期依然不能确定数组的个数,明智的选择是什么也不做,push_back/emplace_back就足够;运行期能确定个数,则应该用reserve,不建议用传递大小的数组构造函数或者调用resize

reserve vs resize

reserveresize函数都能分配足够容纳下指定个数对象的空间。不同之处是resize(或构造函数传递数组个数)会改变数组的size(即当前元素的指针),并且极大的可能性会调用存储对象的**(复制)构造函数**。reserve做的事情就比较纯粹:仅分配所需的空间。

一段代码说明三者的区别:

// file: test.cpp
#include <iostream>
#include <vector>


class Foo {
    public:
        Foo() {
            std::cout << "Foo constructor" << std::endl;
        }
};

int main(int argc, char* argv[]) {
    std::cout << "initialize vector with element number..." << std::endl;
    std::vector<Foo> vec1(5);
    std::cout << "-------------" << std::endl;
    std::cout << "vec1 size:" << vec1.size() << std::endl << std::endl;

    std::cout << "vector resize..." << std::endl;
    std::vector<Foo> vec2;
    vec2.resize(5);
    std::cout << "-------------" << std::endl;
    std::cout << "vec2 size:" << vec2.size() << std::endl << std::endl;


    std::cout << "vector reserve..." << std::endl;
    std::vector<Foo> vec3;
    vec3.reserve(5);
    std::cout << "-------------" << std::endl;
    std::cout << "vec3 size:" << vec3.size() << std::endl << std::endl;

    return 0;
}

gcc编译程序:g++ -std=c++0x -o test -O2 test.cpp,然后./test执行程序,结果如下:

initialize vector with element number...
Foo constructor
Foo constructor
Foo constructor
Foo constructor
Foo constructor
-------------
vec1 size:5

vector resize...
Foo constructor
Foo constructor
Foo constructor
Foo constructor
Foo constructor
-------------
vec2 size:5

vector reserve...
-------------
vec3 size:0

输出结果很显然:无论用构造数组时指定个数,还是resize,都会调用存储类型的默认构造函数并改变size。常规情况下,默认生成的对象没什么用处,最后都会被覆盖掉。如果存储类型的构造函数比较复杂,这两种方式都以大代价做无用功。

reserve就不会,理念简单,性能杠杠的。

注意:编译上述程序,去掉-std=c++0x选项,结果将会不同。原因是vector的构造函数或者resize默认用复制的方式填充,所以构造函数仅调用一次。

总结

细心对比,你会发现reserveresize的区别就像mallocnew的区别(不过malloc是函数,new是操作符):malloc/operator new/reserve得到一块冰冷的内存,什么都没有;new/resize得到的是一个个鲜活的对象。如果仅需存储空间的话,malloc/operator new会更高效。

所以,使用vector时尽量用reserver来提高性能。

参考

  1. std::vector::resize
  2. Does the vector.resize() method calls the default elements constructors when resizing?

转载于:https://my.oschina.net/u/1030865/blog/3065200

<think> 我们正在解析的函数是:`std::vector<myjz::scpi::parser::RawAst>::reserve(size_t _Newcapacity)` 注意:`myjz::scpi::parser::RawAst` 是一个用户自定义类型,但`reserve`函数的行为与`std::vector`的标准`reserve`函数一致,与元素类型无关(除非元素类型的构造函数或析构函数有特殊行为,但`reserve`不涉及元素构造,只涉及内存分配)。 # `std::vector<myjz::scpi::parser::RawAst>::reserve(size_t _Newcapacity)` 函数解析 ## 1. **函数声明** ```cpp void reserve(size_type _Newcapacity); ``` ## 2. **功能描述** - **预分配内存**:该函数用于请求`vector`容器分配至少能容纳`_Newcapacity`个元素的内存空间。 - **容量调整**:如果`_Newcapacity`大于当前`vector`的`capacity()`,则重新分配内存,并将容量增加到`_Newcapacity`或更大(具体取决于内存分配策略);否则,函数不进行任何操作。 - **元素不变性**:调用`reserve()`不会改变`vector`的`size()`,仅改变`capacity()`。元素本身不会被移动或修改,除非重新分配内存导致元素被移动到新的内存位置(此时元素通过移动构造函数或拷贝构造函数转移,原始元素会被销毁)。 ## 3. **参数说明** | 参数 | 类型 | 说明 | |---------------|-------------|----------------------------------------------------------------------| | `_Newcapacity` | `size_type` | 要求的最小新容量(元素个数)。 | ## 4. **关键特性** | 特性 | 说明 | |---------------------------|----------------------------------------------------------------------| | **容量增长策略** | 新容量可能大于`_Newcapacity`(由标准库实现决定) | | **重分配条件** | 仅当`_Newcapacity > capacity()`时发生重分配 | | **迭代器/引用失效** | 若发生重分配,则所有迭代器、引用和指针都会失效;否则保持不变。 | | **复杂度** | 若发生重分配,则与当前`size()`成线性关系(移动或拷贝所有元素);否则为常数复杂度。 | | **异常安全性** | 若发生重分配且元素移动构造函数(或拷贝构造函数)抛出异常,则函数无效果(强异常保证)[^1]。否则不抛出异常。 | ## 5. **使用示例** ```cpp #include <vector> #include "myjz_scpi_parser.h" // 假设包含RawAst的定义 int main() { std::vector<myjz::scpi::parser::RawAst> astVec; // 当前容量(初始为0) std::cout << "Capacity: " << astVec.capacity() << std::endl; // 输出0 // 预分配100个元素的内存 astVec.reserve(100); // 容量变为至少100 std::cout << "Capacity: " << astVec.capacity() << std::endl; // 输出>=100 // 添加元素不会触发重分配,直到超过100 for (int i = 0; i < 100; ++i) { astVec.push_back(RawAst()); // 使用RawAst的默认构造函数 } } ``` ## 6. **边界情况处理** - **无效参数**:如果`_Newcapacity`小于或等于当前`capacity()`,函数立即返回。 - **超大容量**:若`_Newcapacity`超过`max_size()`,则抛出`std::length_error`异常。 - **内存不足**:若内存分配失败,则抛出`std::bad_alloc`异常(或根据分配器设置)。 ## 7. **与`resize()`的区别** | 函数 | 作用 | 容量变化 | 元素变化 | |--------------|----------------------------------|----------------------------|------------------------------| | `reserve(n)` | 仅增加容量(预分配内存) | `capacity() >= n` | 不创建新元素,不改变`size()` | | `resize(n)` | 改变元素数量 | 若`n > capacity()`则增加 | 创建/销毁元素,改变`size()` | ## 8. **性能建议** - **减少重分配**:在已知元素数量时提前调用`reserve()`,避免多次重分配(如循环中`push_back`)。 - **容量管理**:调用`shrink_to_fit()`(C++11)可请求减少容量到`size()`,但实现不一定满足。 §§相关问题§§ 1. `std::vector`的容量增长策略是什么? 2. 如何避免`std::vector`在添加元素时的多次内存重分配? 3. `reserve()`和`shrink_to_fit()`在内存管理中有何互补? 4. 自定义类型作为`vector`元素时,`reserve()`会调用构造函数吗? [^1]: ISO C++标准中关于`vector::reserve`的异常安全保证(23.3.11.3)。 </think># `std::vector<myjz::scpi::parser::RawAst>::reserve(size_t _Newcapacity)` 函数解析 ## 1. **函数声明** ```cpp void reserve(size_type _Newcapacity); ``` ## 2. **功能描述** - **预分配内存**:为`vector`容器预留至少能容纳`_Newcapacity`个元素的内存空间 - **优化性能**:避免后续添加元素时的多次内存重分配 - **容量保证**:调用后`capacity() >= _Newcapacity`(实际容量可能更大) - **不影响元素**:不改变`vector`的`size()`,不会构造或销毁任何元素 ## 3. **参数说明** | 参数 | 类型 | 说明 | |---------------|-------------|----------------------------------------------------------------------| | `_Newcapacity` | `size_type` | 要求的最小新容量(元素个数) | ## 4. **关键特性** | 特性 | 说明 | |---------------------------|----------------------------------------------------------------------| | **重分配条件** | 仅当`_Newcapacity > capacity()`时发生内存重分配 | | **容量增量** | 新容量至少为`_Newcapacity`,具体值由标准库实现决定 | | **迭代器/引用失效** | 若发生重分配,则所有迭代器、引用和指针都会失效;否则保持不变 | | **时间复杂度** | 若重分配:$O(n)$($n$为当前`size()`);否则:$O(1)$ | | **异常安全性** | 强异常保证:失败时容器状态不变(除非元素移动构造函数抛异常)[^1] | ## 5. **使用示例** ```cpp std::vector<myjz::scpi::parser::RawAst> astVec; // 预分配100个RawAst元素的内存 astVec.reserve(100); // 现在可以高效添加元素(不会触发重分配) for (int i = 0; i < 100; ++i) { astVec.push_back(RawAst()); } ``` ## 6. **边界情况处理** ```cpp // 情况1:请求容量小于当前容量 astVec.reserve(50); // 无操作,容量不变 // 情况2:请求超大容量(超过max_size()) astVec.reserve(astVec.max_size() + 1); // 抛出std::length_error // 情况3:内存分配失败 try { astVec.reserve(1'000'000'000); } catch (const std::bad_alloc& e) { // 处理内存不足 } ``` ## 7. **与相关函数对比** | 函数 | 作用 | 容量变化 | 元素变化 | |--------------|----------------------------------|----------------------------|------------------------------| | `reserve(n)` | 仅增加容量(预分配内存) | `capacity() >= n` | 不创建新元素,不改变`size()` | | `resize(n)` | 改变元素数量 | 若`n > capacity()`则增加 | 创建/销毁元素,改变`size()` | | `shrink_to_fit()` | 请求减少容量到`size()` | `capacity() >= size()` | 不影响元素 | ## 8. **最佳实践** 1. **已知元素数量时预分配**: ```cpp std::vector<RawAst> astVec; astVec.reserve(expected_element_count); // 避免多次重分配 ``` 2. **避免过度预留**: ```cpp // 合理估算最大值 astVec.reserve(max_possible_elements); ``` 3. **与`emplace_back`配合**: ```cpp astVec.reserve(100); astVec.emplace_back(arg1, arg2); // 直接构造元素,避免拷贝 ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值