ARTICLE · 1247863
libcxx stl源码分析-vector的resize函数
class A {public:A(int) {std::cout << "A(int)\n";}A() {std::cout << "A()\n";};~A() {std::cout << "~A()\n";};intmain(){std::vector<A> a{5};a.resize(10);a.resize(2);return 0;}

template <class_Tp, class_Allocator>void vector<_Tp, _Allocator>::resize(size_type __new_size) {size_type __current_size = size();if (__current_size < __new_size) {if (__new_size <= capacity()) {__construct_at_end(__new_size - __current_size);} else {_SplitBuffer __v(__recommend(__new_size), __current_size, __layout_.__alloc());__v.__construct_at_end(__new_size - __current_size);__layout_.__relocate(__v);}} else if (__current_size > __new_size) {this->__destruct_at_end(this->__layout_.__begin_ptr() + __new_size);}}
this->__destruct_at_end(this->__layout_.__begin_ptr() + __new_size);void __destruct_at_end(pointer __new_last) _NOEXCEPT {size_type __old_size = size();std::__allocator_destroy(__layout_.__alloc(), __new_last, __layout_.__end_ptr());__layout_.__set_bound_using_pointer(__new_last);__annotate_shrink(__old_size);}
我们主要讲解下这个函数
std::__allocator_destroy(__layout_.__alloc(), __new_last, __layout_.__end_ptr());template <class _Tp, class =__enable_if_t<__has_destroy<allocator_type, _Tp*>::value> >static void destroy(allocator_type& __a, _Tp* __p) {_LIBCPP_SUPPRESS_DEPRECATED_PUSH__a.destroy(__p);_LIBCPP_SUPPRESS_DEPRECATED_POP}template <class _Tp, class = void, class =__enable_if_t<!__has_destroy<allocator_type, _Tp*>::value> >static void destroy(allocator_type&, _Tp* __p) {#if _LIBCPP_STD_VER > 17_VSTD::destroy_at(__p);#else__p->~_Tp();#endif}
这段是allocator_traits里destroy的两个重载,核心是用 SFINAE 在编译期选择"调用 allocator 自己的destroy"还是"直接调用析构函数"。这是allocator_traits为兼容旧式 allocator 而设计的经典检测机制。
准 allocator 模型里,destroy有两条路径:
allocator 自己提供了destroy成员(C++17 前的旧式 allocator 可能有)→ 应该调用__a.destroy(__p),尊重用户的定制。
allocator 没有提供destroy(C++17 后的 allocator 通常只有allocate/deallocate,construct/destroy已被弃用/移除)→ 直接调用p->~T()(或 C++17 后的destroy_at)。
两个重载通过__enable_if_t互斥地启用,实现编译期二选一。
第一个重载:allocator 有 destroy
启用条件:__has_destroy::value为真——即 allocator 确实有可调用的destroy(_Tp*)成员。
技巧:用第二个模板参数的默认值做 SFINAE
template <class _Tp, class = __enable_if_t<条件>>第二个模板参数class没有名字,其默认值是__enable_if_t<条件>。
当条件为真时,__enable_if_t<...>是void(或其他类型),默认参数合法,重载可用。
当条件为假时,__enable_if_t<...>触发替换失败(SFINAE),该重载从候选集中被静默移除,不报错。
这样两个重载就能互斥启用,且不需要给模板参数命名(因为只是"开关",不用它)。
函数体:
_LIBCPP_SUPPRESS_DEPRECATED_PUSH__a.destroy(__p);_LIBCPP_SUPPRESS_DEPRECATED_POP
调用 allocator 的destroy成员。
外面包了_LIBCPP_SUPPRESS_DEPRECATED_PUSH/POP:因为 C++17 起allocator::destroy被标记为弃用(deprecated),libc++ 内部仍要调用它(为兼容旧 allocator),所以临时压掉弃用告警,避免内部实现自己触发警告。
注释性的PUSH/POP对应编译器 pragma(如#pragmaclang diagnostic push/pop),作用域内抑制-Wdeprecated-declarations。
_LIBCPP_CONSTEXPR_AFTER_CXX17:C++17 起该函数可为constexpr(C++20 起std::allocator::destroy被移除,但allocator_traits::destroy仍是 constexpr 能力)。因为析构在常量求值中受限制,这个宏是 libc++ 常见的"条件 constexpr"处理。
第二个重载:allocator 没有 destroy
启用条件:!__has_destroy<...>::value——allocator没有destroy成员。
template <class _Tp, class = void, class = __enable_if_t<条件>>第一个重载的模板参数列表是>(两个参数)。
第二个是>(三个参数)。
两者参数个数不同,是为了避免在某些情况下两个重载的签名"看起来相同"导致歧义/重定义。由于条件 A、B 互斥,实际永远只有一个可用,但模板参数列表的形态差异进一步保证它们不是同一模板的重复声明。
中间的class = void是占位,使第二个__enable_if_t落在第三个位置。
#if _LIBCPP_STD_VER > 17_VSTD::destroy_at(__p);#else__p->~_Tp();#endif
C++17 之后:调用std::destroy_at(__p)。destroy_at是 C++17 引入的,语义是"对p指向的对象调用析构",且对数组类型有特殊处理(逐元素析构)。它比p->~T()更通用、更符合现代写法。
C++17 之前:没有destroy_at,直接__p->~_Tp()显式调用析构函数。
参数allocator_type&无名——因为这条路径不需要 allocator,只是为保持与第一个重载相同的调用接口(allocator_traits::destroy(a, p)总是传 allocator,但这里用不上)。
对resize(更小)而言:
销毁被裁掉的对象:对[begin + __new_size, 原 end)这段共__current_size - __new_size个元素逐个调用析构(通过allocator_traits::destroy,尊重 allocator 定制)。
end前移:end = begin + __new_size,有效区间变成[begin, begin + __new_size)。
sanitizer 注解:更新容器的可访问范围。















































































