C++ 动态内存
C++ 动态内存管理
Section titled “C++ 动态内存管理”理解动态内存管理在 C++ 中至关重要。C++ 程序通常使用两个主要区域进行内存分配:
- 栈(The Stack): 用于局部变量、函数参数和返回地址的内存。分配和释放是自动且快速的(LIFO - 后进先出)。大小通常有限。
- 堆(The Heap)/自由存储区(Free Store): 程序执行期间可用于动态分配的一大块内存池。分配和释放是手动的(或通过智能指针管理),可能较慢,但在编译时不知道数据大小或生命周期,或者数据需要比创建它的函数存活更久时是必需的。
动态内存分配允许您在运行时从堆中请求内存,此时所需的精确数量事先未知。
裸指针:new 和 delete(谨慎使用!)
Section titled “裸指针:new 和 delete(谨慎使用!)”历史上,C++ 使用 new 运算符在堆上分配内存,使用 delete 运算符释放内存。
// 为一个 double 分配内存double* pValue = new double;*pValue = 3.14159; // 使用已分配的内存
// ... 稍后 ...
delete pValue; // *** 关键:释放内存 ***pValue = nullptr; // 好习惯:避免悬空指针分配数组:
// 为包含 10 个整数的数组分配内存int* pArray = new int[10];pArray[0] = 100; // 使用已分配的数组
// ... 稍后 ...
delete[] pArray; // *** 关键:对数组使用 delete[] ***pArray = nullptr;使用原始 new/delete 的问题:
- 内存泄漏(Memory Leaks): 忘记调用
delete或delete[]会导致内存泄漏(内存仍然分配着但无法使用)。 - 悬空指针(Dangling Pointers): 在调用
delete后通过指针访问内存会导致未定义行为。 - 重复释放(Double Deletion): 对同一个指针调用
delete或delete[]两次会导致未定义行为。 - 异常安全(Exception Safety): 如果在
new和delete之间发生异常,delete可能被跳过,导致泄漏。 - 所有权混淆(Ownership Confusion): 通常不清楚代码的哪一部分负责删除内存。
处理分配失败:默认情况下,如果 new 未能分配内存(例如,内存不足),它会抛出类型为 std::bad_alloc(定义在 <new> 中)的异常。您应该使用 try...catch 块来处理这种情况。
#include <iostream>#include <new> // 需要包含 std::bad_alloc
int main() { double* pHugeArray = nullptr; try { // 尝试分配大量内存(可能失败) pHugeArray = new double[1000000000000]; std::cout << "内存分配成功(不太可能!)" << std::endl; // 在此处使用 pHugeArray... } catch (const std::bad_alloc& e) { std::cerr << "内存分配失败: " << e.what() << std::endl; // 适当处理错误 return 1; // 退出或恢复 }
// 如果分配成功,记住要删除 delete[] pHugeArray; pHugeArray = nullptr;
return 0;}还有一个非抛出异常的版本:new(std::nothrow) type。它在失败时返回 nullptr 而不是抛出异常,需要手动进行空指针检查。然而,在现代 C++ 中,通常更推荐使用抛出异常的版本。
现代 C++:智能指针(推荐方式)
Section titled “现代 C++:智能指针(推荐方式)”现代 C++ 强烈建议使用智能指针(smart pointers)(定义在 <memory> 中)来管理动态内存。它们使用 RAII(Resource Acquisition Is Initialization,资源获取即初始化)原则自动化释放过程:在智能指针的构造函数中获取内存,并在其析构函数中(当智能指针超出作用域时)自动释放。
std::unique_ptr: 表示独占所有权。任何时候只有一个 unique_ptr 可以指向某个资源。当 unique_ptr 被销毁(超出作用域)时,它会自动删除管理的*对象。它轻量级,是单所有权场景的首选。
#include <iostream>#include <memory> // 需要包含智能指针#include <vector>
class MyResource {public: MyResource() { std::cout << "MyResource Created\n"; } ~MyResource() { std::cout << "MyResource Destroyed\n"; } void use() { std::cout << "Using MyResource\n"; }};
int main() { // 创建一个管理 MyResource 对象的 unique_ptr // 使用 std::make_unique (C++14+) 进行更安全的分配 auto ptr1 = std::make_unique<MyResource>();
// 通过 unique_ptr 使用资源(行为类似于裸指针) ptr1->use();
// 用于数组的 unique_ptr auto ptrArray = std::make_unique<int[]>(5); // 包含 5 个整数的数组 ptrArray[0] = 10; std::cout << "ptrArray[0] = " << ptrArray[0] << std::endl;
// 无需手动调用 delete 或 delete[]! // 当 ptr1 和 ptrArray 超出作用域时,内存会自动释放。 std::cout << "Exiting main...\n"; return 0;}// 输出将显示 MyResource Created, Using MyResource, ptrArray[0]=10, Exiting main..., MyResource Destroyedstd::shared_ptr: 表示共享所有权。多个 shared_ptr 实例可以指向同一个资源。只有当指向该资源的*最后一个 shared_ptr 被销毁或重置时,资源才会被删除。它内部使用引用计数(reference counting),与 unique_ptr 相比会增加一些开销。
#include <iostream>#include <memory>#include <vector>
class MyResource { // 定义和前面 unique_ptr 示例中的 MyResource 相同public: MyResource() { std::cout << "MyResource Created\n"; } ~MyResource() { std::cout << "MyResource Destroyed\n"; } void use() { std::cout << "Using MyResource\n"; }};
int main() { // 使用 std::make_shared(推荐方式) auto shared_res = std::make_shared<MyResource>(); std::cout << "Initial use count: " << shared_res.use_count() << std::endl; // 通常是 1
std::vector<std::shared_ptr<MyResource>> ptr_vec; ptr_vec.push_back(shared_res); // 复制会增加使用计数 std::cout << "Use count after vector push_back: " << shared_res.use_count() << std::endl; // 通常是 2
auto another_shared = shared_res; // 另一个复制 std::cout << "Use count after assignment: " << another_shared.use_count() << std::endl; // 通常是 3
shared_res->use();
// 指针超出作用域,使用计数减少。 // 当使用计数降至 0 时,资源被删除。 std::cout << "Exiting main...\n"; return 0;}// 输出显示使用计数的变化以及资源在结束时被销毁。std::weak_ptr: shared_ptr 的非拥有(non-owning)伴侣。它指向一个由 shared_ptr 管理的对象,但不影响引用计数。用于打破涉及 shared_ptr 的潜在循环引用(circular references)。
总结: 优先使用智能指针(std::unique_ptr、std::shared_ptr)而非原始的 new 和 delete。使用 std::make_unique 和 std::make_shared 进行更安全且可能更高效的分配。这种方法很大程度上消除了内存泄漏,并使代码更安全、更易于管理。
对象的动态内存
Section titled “对象的动态内存”当您使用 new 或智能指针动态分配对象时,它们的构造函数会被调用。当它们被释放(通过 delete 或智能指针析构)时,它们的析构函数会被调用。
#include <iostream>#include <memory>
class Box {public: Box() { std::cout << "Constructor called!\n"; } ~Box() { std::cout << "Destructor called!\n"; }};
int main() { std::cout << "Creating unique_ptr for single Box:\n"; { auto singleBoxPtr = std::make_unique<Box>(); // 构造函数在此处调用 } // singleBoxPtr 超出作用域时,析构函数在此处调用 std::cout << "\nCreating unique_ptr for Box array:\n"; { // 分配包含 4 个 Box 对象的数组 auto myBoxArray = std::make_unique<Box[]>(4); // 4 个构造函数调用在此处发生 } // myBoxArray 超出作用域时,4 个析构函数调用在此处发生 std::cout << "\nExiting main.\n"; return 0;}输出演示了由 std::unique_ptr 自动管理的构造函数/析构函数调用:
Creating unique_ptr for single Box:Constructor called!Destructor called!
Creating unique_ptr for Box array:Constructor called!Constructor called!Constructor called!Constructor called!Destructor called!Destructor called!Destructor called!Destructor called!
Exiting main.