std::vector<T> clear详解

这是一个非常核心的问题。在 C++ 标准库中,std::vector::clear() 的行为是非常明确的。为了让你彻底理解,我们从逻辑行为源码实现(以 GCC 的 libstdc++ 为例)以及内存状态三个维度来剖析。


1. clear() 执行了什么?(逻辑行为)

当你调用 vec.clear() 时,它只做两件事:

  1. 销毁对象:按顺序调用容器内所有元素的析构函数。
  2. 重置大小:将 size 设置为 0。

但是:不会释放底层分配的堆内存,也不会改变容器的容量(Capacity)。


2. 源码深度解析(以 libstdc++ 为例)

在 GCC 的实现中,vector 的数据由三个指针管理:

  • _M_start: 指向第一个元素。
  • _M_finish: 指向最后一个元素之后的位置(即 size 的终点)。
  • _M_end_of_storage: 指向已分配堆内存的末尾(即 capacity 的终点)。

clear() 的源码大致如下:

1
2
3
4
5
6
7
8
9
void clear() _GLIBCXX_NOEXCEPT
{
// 1. 调用销毁函数,范围是从第一个元素到最后一个元素
_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,
_M_get_Tp_allocator());

// 2. 将“已用空间”的指针指向起点,即 size 归零
this->_M_impl._M_finish = this->_M_impl._M_start;
}

_Destroy 做了什么?

如果你的元素是复杂类型(如 std::string 或自定义 Class):

  • 它会遍历每一个元素,通过类似 ptr->~T() 的方式手动调用析构函数。

如果你的元素是平凡类型(Trivially Destructible,如 int, floatMTileID 这种简单的结构体):

  • 编译器会进行模板特化优化_Destroy 实际上会变成一个空操作(No-op)。这意味着 clear() 仅仅是移动了一下指针。

3. 内存回收与容量变化

(1) 堆内存是否回收?

不回收。
_M_end_of_storage 指针在 clear() 执行后保持不动。vector 依然占用着原先申请的那块堆空间。

  • 原因:这是为了性能。如果立刻释放内存,下次 push_back 时又需要昂贵的 malloc 系统调用。

(2) 容量(Capacity)是否变化?

不变化。
vec.capacity()clear() 前后是相等的。

(3) 元素持有资源的释放?

这是一个陷阱。

  • 如果 vector<MyObject>clear() 会销毁 MyObject,释放其内部资源。
  • 如果 vector<MyObject*>原始指针),clear() 只会销毁“指针变量本身”(即那 8 个字节的地址),而不会调用 delete 指向的对象。这会导致内存泄漏。

4. 结合你之前的场景:Mali GPU 与内存碎片

在你之前提到的 MapTile(地图瓦片) 场景中,频繁地对 std::vector 进行 clear() 有利有弊:

  • :避免了频繁的堆内存申请/释放(malloc/free),减少了 CPU 开销和内存碎片。
    • 内存占用高峰:如果某一次 vector 扩容到了非常大(例如 10MB),即使 clear() 后,这 10MB 也会一直被该对象占着不还给系统。
    • 隐蔽的竞态:虽然 size 是 0 了,但底层的 Buffer 依然在。如果此时 GPU 驱动因为某种异步 Bug 还在读取这块地址(即便此时 size 逻辑上为 0),它可能读到旧数据而不是触发崩溃;或者如果此时发生重分配,老地址被释放,才会真正崩溃。

5. 如果你真的想释放内存怎么办?

如果你觉得 vector 占用的内存太大了,想还给系统,有两种标准做法:

方案 A:shrink_to_fit() (C++11)

1
2
vec.clear();
vec.shrink_to_fit(); // 请求减小容量以匹配大小
  • 注意:这会触发一次内存重新分配和拷贝,开销较大。

方案 B:Swap 技巧(最彻底,C++98 时代至今通用)

1
2
// 创建一个空的临时 vector 与原 vector 交换
std::vector<T>().swap(vec);
  • 效果:临时对象获得了 vec 的大块内存,然后临时对象生命周期结束,立刻触发真正的 freevec 彻底回到初始状态(Capacity = 0)。

总结给你的建议:

对于地图引擎这种高性能场景,通常不建议释放内存
你应该利用 clear() 不释放内存的特性,通过 reserve() 预留好空间,实现内存池化的效果。只有当检测到内存极度紧张,或者该模块长时间不再使用时,才考虑用 Swap 技巧彻底释放。