为何libstdc++的std::vector构造函数实现不会引发内存泄漏?
问题描述
我正在使用GCC 7.3.0及libstdc++库。以下是std::vector的构造函数实现:
vector(initializer_list<value_type> __l, const allocator_type& __a = allocator_type()) : _Base(__a) { _M_range_initialize(__l.begin(), __l.end(), random_access_iterator_tag()); } // 由上述第二个initialize_dispatch调用 template<typename _ForwardIterator> void _M_range_initialize(_ForwardIterator __first, _ForwardIterator __last, std::forward_iterator_tag) { const size_type __n = std::distance(__first, __last); this->_M_impl._M_start = this->_M_allocate(__n); this->_M_impl._M_end_of_storage = this->_M_impl._M_start + __n; this->_M_impl._M_finish = std::__uninitialized_copy_a(__first, __last, this->_M_impl._M_start, _M_get_Tp_allocator()); }
我认为,在_M_range_initialize(_ForwardIterator,_ForwardIterator,std::forward_iterator_tag)函数中,当std::__uninitialized_copy_a抛出异常时,该函数中已分配的this->_M_impl._M_start不会被释放,这意味着该实现会导致内存泄漏。但libstdc++是经过充分测试的知名库,我的理解肯定有误,想请教为何该实现不会产生内存泄漏?
解答
你观察到的代码片段确实看起来有风险,但实际上libstdc在这里的异常安全是靠vector的基类析构逻辑和C的异常处理规则来保证的,具体原因如下:
构造异常触发已构造子对象的析构
当_M_range_initialize中std::__uninitialized_copy_a抛出异常时,vector的构造函数还没有完成执行。根据C++的异常处理规则,所有已经构造完成的基类和成员变量都会被自动析构。vector的基类_Base封装了内存管理的核心结构_M_impl,它的析构函数会检查_M_impl._M_start是否已经分配了内存:如果内存已分配但元素未完全初始化(即_M_finish不等于_M_end_of_storage),就会调用分配器的deallocate方法释放这块内存。__uninitialized_copy_a的异常协作
std::__uninitialized_copy_a遵循基本异常安全保证:如果在复制构造元素时抛出异常,它会先把已经构造好的元素逐个析构,然后终止复制流程,但不会主动释放底层内存。这块内存的释放工作就交给了vector基类的析构函数——因为此时vector对象还没完全构造完成,基类的析构会自动处理未完成的内存分配。分配器的一致性保障
这里用于分配内存的_M_allocate和后续析构时使用的deallocate,都是通过_M_get_Tp_allocator()获取的同一个分配器实例(和vector构造时传入的分配器一致),所以基类析构时可以正确识别并释放这块内存。
简单来说,_M_range_initialize本身不需要显式的try-catch块,C++的异常机制会自动触发基类的清理逻辑,从而避免了内存泄漏。
内容的提问来源于stack exchange,提问作者yumetodo

