手动管理内存实现动态数组需用new/delete[]配合裸指针,维护m_data、m_size、m_capacity三个成员,扩容时用2倍策略、移动构造、显式析构,注意对齐与noexcept。

怎么手动管理内存实现动态数组
直接用 new 和 delete[] 配合裸指针是最底层的方式,但必须自己管容量、大小、扩容逻辑。不是不能写,而是稍有疏忽就会内存泄漏或越界——比如忘了在拷贝构造里深拷贝,或者 resize 时没正确释放旧内存。
实操建议:
- 用两个成员变量:
m_data(T*指针)和m_size(当前元素个数),再加一个m_capacity(已分配空间大小) - 首次分配建议设容量为 1 或 2,别从 0 开始;否则每次
push_back都要 realloc,性能极差 -
push_back前必须检查m_size == m_capacity,触发扩容:通常用m_capacity * 2,别用 +1(摊还成本爆炸) - 扩容时先
new T[new_capacity],逐个调用new (&dest[i]) T(std::move(src[i]))移动构造(C++11+),再显式调用旧对象的析构函数,最后delete[] m_data
为什么不能直接用 std::vector 的部分源码来简化
因为 std::vector 依赖 std::allocator、std::uninitialized_copy 等设施,还做了异常安全、SFINAE、迭代器类别等大量适配。抄几行 data_ 和 size_ 看似简单,但一旦涉及异常(比如构造函数抛出)、移动语义或自定义类型,裸指针方案立刻崩。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 插入含资源的对象(如
std::string)时,只做位拷贝(memcpy),导致多个对象指向同一堆内存,析构时报 double-free - 没声明移动构造/移动赋值,对象在 vector 扩容时被反复拷贝,性能陡降且可能失败
- 没处理
noexcept,导致std::vector在某些优化路径下拒绝移动而改用拷贝
简易版该暴露哪些接口才够用
够用 ≠ 全,重点是让使用者不掉坑。比如 operator[] 不做边界检查(和 std::vector::operator[] 一致),但必须提供 at() 做带异常的访问——否则调试时根本不知道哪越界了。
实操建议:
- 必有:构造函数(空、带初始容量)、
push_back(const T&)、push_back(T&&)、size()、capacity()、empty()、operator[]、at()、pop_back()、析构函数 - 可选但推荐:
reserve()(预分配,避免多次扩容)、clear()(只清 size,不释放内存)、移动构造/赋值(用noexcept标记) - 别急着加
insert/erase—— 它们需要迭代器支持,一写就牵扯到 traits、tag dispatch、std::distance,复杂度指数上升
容易被忽略的细节:对齐与 noexcept
如果存的是 alignas(16) 类型(比如 SIMD 结构体),仅用 new char[capacity * sizeof(T)] 分配内存会导致地址不对齐,触发硬件异常。而 std::allocator 内部会调用 std::align 修正。
另一个隐形炸弹是 noexcept:哪怕你所有操作都不抛异常,只要没显式写 noexcept,编译器就默认函数可能抛,导致 std::vector 在 move-aware 容器中退化成拷贝。
所以:
- 手动分配内存时,用
operator new(size, std::align_val_t{alignof(T)})替代裸new char[] - 所有不抛异常的函数(构造、移动、
push_back等)末尾加上noexcept - 移动操作里,确保
T的移动构造/赋值也是noexcept,否则你的移动函数会被视为可能抛异常
这些点不写代码时看不出问题,一跑 ASan 或上生产环境就卡住半天——尤其对齐问题,在 x86 可能只是慢,在 ARM 直接 crash。










