第12章:动态内存(Dynamic Memory)
一、导读
1.1 本章学习目标
第12章讲解C++的动态内存管理。本章核心目标:
- 掌握new和delete:动态内存分配和释放
- 理解智能指针:shared_ptr、unique_ptr、weak_ptr
- 学会使用make_shared和make_unique
- 理解内存泄漏和悬空指针
- 掌握动态数组与vector的比较
1.2 为什么本章如此重要
动态内存管理是C++中最容易出错的部分。内存泄漏、悬空指针、重复释放——这些问题在大型程序中可能导致灾难性后果。C++11引入的智能指针大大简化了内存管理,是现代C++编程的重要特性。
1.3 核心问题引导
new和malloc有什么区别?
shared_ptr的引用计数是如何工作的?
unique_ptr和shared_ptr各适合什么场景?
什么是循环引用?weak_ptr如何解决?
二、核心概念详解
12.1 new和delete
cpp
#include <iostream>
#include <string>
int main() {
// 单个对象
int *p1 = new int(42);
std::cout << *p1 << std::endl;
delete p1;
// 动态数组
int *arr = new int[5]{1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
delete[] arr; // 数组必须用delete[]
// 对象
std::string *sp = new std::string("Hello");
std::cout << *sp << std::endl;
delete sp;
// new失败时抛出bad_alloc异常
try {
int *huge = new int[1000000000000];
} catch (const std::bad_alloc &e) {
std::cerr << "Allocation failed: " << e.what() << std::endl;
}
// nothrow版本
int *p = new (std::nothrow) int[1000000000000];
if (!p) {
std::cerr << "Allocation failed" << std::endl;
}
return 0;
}12.2 智能指针
cpp
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Student {
std::string name;
int age;
public:
Student(std::string n, int a) : name(n), age(a) {
std::cout << "Constructor: " << name << std::endl;
}
~Student() {
std::cout << "Destructor: " << name << std::endl;
}
void display() const {
std::cout << name << ", " << age << std::endl;
}
};
int main() {
// shared_ptr:共享所有权,引用计数
auto sp1 = std::make_shared<Student>("Alice", 20);
sp1->display();
{
auto sp2 = sp1; // 引用计数+1
std::cout << "Count: " << sp1.use_count() << std::endl; // 2
}
// sp2离开作用域,引用计数-1
std::cout << "Count: " << sp1.use_count() << std::endl; // 1
// unique_ptr:独占所有权,不能复制
auto up1 = std::make_unique<Student>("Bob", 21);
// auto up2 = up1; // 错误:不能复制
auto up2 = std::move(up1); // 转移所有权
// up1现在为空
// weak_ptr:不增加引用计数,解决循环引用
std::shared_ptr<Student> sp3 = std::make_shared<Student>("Charlie", 19);
std::weak_ptr<Student> wp = sp3;
std::cout << "sp3 count: " << sp3.use_count() << std::endl; // 1
std::cout << "wp expired: " << wp.expired() << std::endl; // 0
if (auto locked = wp.lock()) {
locked->display();
}
// 动态对象数组
std::vector<std::shared_ptr<Student>> students;
students.push_back(std::make_shared<Student>("David", 22));
students.push_back(std::make_shared<Student>("Eve", 20));
for (const auto &s : students) {
s->display();
}
return 0;
}12.3 循环引用和weak_ptr
cpp
#include <iostream>
#include <memory>
class Node {
public:
std::string name;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用weak_ptr避免循环引用
Node(std::string n) : name(n) {
std::cout << "Node created: " << name << std::endl;
}
~Node() {
std::cout << "Node destroyed: " << name << std::endl;
}
};
int main() {
auto node1 = std::make_shared<Node>("Node1");
auto node2 = std::make_shared<Node>("Node2");
node1->next = node2;
node2->prev = node1; // weak_ptr不增加引用计数
std::cout << "node1 count: " << node1.use_count() << std::endl; // 1
std::cout << "node2 count: " << node2.use_count() << std::endl; // 2
// 如果使用shared_ptr作为prev,会导致循环引用
// node1和node2永远不会被销毁
return 0;
}12.4 allocator类
cpp
#include <iostream>
#include <memory>
#include <string>
#include <vector>
int main() {
// allocator:分离内存分配和对象构造
std::allocator<std::string> alloc;
// 分配未初始化的内存
auto const p = alloc.allocate(3);
// 在分配的内存中构造对象
auto q = p;
alloc.construct(q++, "Hello");
alloc.construct(q++, "World");
alloc.construct(q++, "!");
// 使用
for (auto it = p; it != q; ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
// 析构对象
while (q != p) {
alloc.destroy(--q);
}
// 释放内存
alloc.deallocate(p, 3);
// 使用uninitialized_copy
std::vector<int> v = {1, 2, 3, 4, 5};
std::allocator<int> int_alloc;
auto pi = int_alloc.allocate(5);
std::uninitialized_copy(v.begin(), v.end(), pi);
for (int i = 0; i < 5; i++) {
std::cout << pi[i] << " ";
}
std::cout << std::endl;
int_alloc.deallocate(pi, 5);
return 0;
}三、实战案例
3.1 对象池
cpp
#include <iostream>
#include <memory>
#include <vector>
#include <stack>
class Object {
int id;
static int next_id;
public:
Object() : id(next_id++) {
std::cout << "Object " << id << " created" << std::endl;
}
~Object() {
std::cout << "Object " << id << " destroyed" << std::endl;
}
void reset() { /* 重置状态 */ }
int getId() const { return id; }
};
int Object::next_id = 0;
class ObjectPool {
std::stack<std::shared_ptr<Object>> pool;
size_t max_size;
public:
ObjectPool(size_t size) : max_size(size) {
for (size_t i = 0; i < size; i++) {
pool.push(std::make_shared<Object>());
}
}
std::shared_ptr<Object> acquire() {
if (pool.empty()) {
throw std::runtime_error("Pool exhausted");
}
auto obj = pool.top();
pool.pop();
return obj;
}
void release(std::shared_ptr<Object> obj) {
obj->reset();
pool.push(obj);
}
size_t available() const { return pool.size(); }
};
int main() {
ObjectPool pool(3);
std::cout << "Available: " << pool.available() << std::endl;
auto obj1 = pool.acquire();
auto obj2 = pool.acquire();
std::cout << "Available: " << pool.available() << std::endl;
pool.release(obj1);
std::cout << "Available: " << pool.available() << std::endl;
return 0;
}四、本章小结
第12章介绍了动态内存管理:new/delete、智能指针(shared_ptr、unique_ptr、weak_ptr)、循环引用问题、allocator类。现代C++推荐使用智能指针管理动态内存。
五、思考与练习
思考题
shared_ptr的引用计数是线程安全的吗?
unique_ptr的自定义删除器如何使用?
如何检测内存泄漏?
什么时候应该用new/delete,什么时候用智能指针?
编程练习
实现一个简单的智能指针类。
使用shared_ptr实现一个观察者模式。
实现一个内存池,管理固定大小的对象。