C++20中如何让持有可移动引用计数指针的std::vector高效扩容?
C++20中std::vector针对引用计数类的高效扩容方案
你的引用计数类特性是关键:移动操作noexcept,移动后源对象指针置为nullptr且析构为空操作。针对这种场景,C++20中可以通过以下方式让std::vector实现你期望的批量内存复制式扩容:
核心思路
你的类的移动构造+源对象析构的组合效果,等价于直接复制内存块到新地址,然后直接释放旧内存(不调用源对象析构)——因为源对象析构本就是空操作,复制内存后新对象持有有效指针,旧内存直接释放不会触发错误的引用计数操作。
具体实现方式
利用编译器的可重定位类型扩展
主流编译器(GCC、Clang)在C++20中提供了非标准扩展,允许标记类为可平凡重定位,这样std::vector扩容时会跳过逐个移动构造+析构的步骤,直接用memcpy/memmove批量复制内存块:
// 标记整个类为可平凡重定位(GCC/Clang扩展) [[gnu::trivially_relocatable]] class RefCountedPtr { // 唯一数据成员:指向侵入式引用计数对象的指针 IntrusiveRefCountObj* ptr = nullptr; public: // Rule of 5 均声明为noexcept RefCountedPtr() noexcept = default; ~RefCountedPtr() noexcept { if (ptr) { ptr->decrement_ref(); } } RefCountedPtr(const RefCountedPtr& other) noexcept { ptr = other.ptr; if (ptr) ptr->increment_ref(); } RefCountedPtr(RefCountedPtr&& other) noexcept { ptr = other.ptr; other.ptr = nullptr; } RefCountedPtr& operator=(const RefCountedPtr& other) noexcept { if (this != &other) { if (ptr) ptr->decrement_ref(); ptr = other.ptr; if (ptr) ptr->increment_ref(); } return *this; } RefCountedPtr& operator=(RefCountedPtr&& other) noexcept { if (this != &other) { if (ptr) ptr->decrement_ref(); ptr = other.ptr; other.ptr = nullptr; } return *this; } };
标记后,std::vector<RefCountedPtr>扩容时会自动采用批量内存复制,效率远高于逐个移动构造。
标准层面的替代方案(C++20兼容)
如果依赖编译器扩展不可接受,你可以手动规避频繁扩容:
- 提前调用
std::vector::reserve()预分配足够空间,从根源减少扩容次数; - 若必须扩容,可自行分配新内存,用
std::memcpy批量复制旧内存中的指针到新内存,再通过std::vector::swap()替换内部缓冲区(注意手动释放旧缓冲区内存,且不调用旧元素的析构函数)。不过这种方式需要绕过std::vector的默认行为,风险较高,不推荐。
注意事项
- 只有当你的类满足“移动后源对象析构无副作用”时,才能使用批量复制的方式——否则直接复制内存会导致源对象析构时错误修改引用计数;
- 若使用编译器扩展,需确认目标编译器支持
[[gnu::trivially_relocatable]](GCC 10+、Clang 11+均支持)。
内容的提问来源于stack exchange,提问作者John Yates
相关产品推荐
相关产品推荐

