C++ Primer 第12章:动态内存
12.1 动态内存与智能指针
12.1.1 为什么需要动态内存?
内存区域:
┌─────────────────────────────────────────────────────┐
│ 静态内存(Static) │
│ - 全局变量、static变量 │
│ - 程序启动时分配,程序结束时销毁 │
├─────────────────────────────────────────────────────┤
│ 栈内存(Stack) │
│ - 局部变量、函数参数 │
│ - 函数调用时分配,函数返回时销毁 │
├─────────────────────────────────────────────────────┤
│ 堆内存(Heap / 自由存储区) │
│ - new 分配,delete 释放 │
│ - 生命周期由程序员控制 │
│ - 智能指针自动管理 │
└─────────────────────────────────────────────────────┘
动态内存的使用场景:
1. 运行时才知道需要多少对象
2. 运行时才知道对象的类型
3. 需要在多个对象间共享数据
12.1.2 直接使用 new 和 delete 的问题
// raw_pointer_problems.cpp -- 裸指针的问题
#include <iostream>
#include <string>
int main()
{
using namespace std;
// ===== 问题1:忘记释放内存(内存泄漏)=====
{
int* p = new int(42);
// ... 某些操作 ...
// 忘记 delete p; // 内存泄漏!
}
// ===== 问题2:重复释放(未定义行为)=====
{
int* p = new int(42);
delete p;
// delete p; // ❌ 重复释放!未定义行为
}
// ===== 问题3:使用已释放的内存(悬空指针)=====
{
int* p = new int(42);
delete p;
// *p = 100; // ❌ 悬空指针!未定义行为
p = nullptr; // 好习惯:释放后置nullptr
}
// ===== 问题4:异常安全问题 =====
{
int* p = new int(42);
// 如果这里抛出异常,delete p 不会执行
// throw runtime_error("error"); // 内存泄漏!
delete p;
}
// ===== 解决方案:使用智能指针 =====
// 智能指针自动管理内存,解决上述所有问题
return 0;
}
12.1.3 shared_ptr
// shared_ptr_demo.cpp -- shared_ptr详解
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Resource
{
public:
string name;
Resource(const string& n) : name(n)
{
cout << "[创建] " << name << endl;
}
~Resource()
{
cout << "[销毁] " << name << endl;
}
void use() const { cout << "[使用] " << name << endl; }
};
int main()
{
using namespace std;
// ===== 创建 shared_ptr =====
// 方式1:make_shared(推荐,更安全高效)
auto sp1 = make_shared<Resource>("资源A");
// 方式2:直接构造(不推荐)
shared_ptr<Resource> sp2(new Resource("资源B"));
// ===== 引用计数 =====
cout << "\n引用计数:" << endl;
cout << "sp1.use_count() = " << sp1.use_count() << endl; // 1
{
auto sp3 = sp1; // 拷贝,引用计数+1
auto sp4 = sp1; // 拷贝,引用计数+1
cout << "sp1.use_count() = " << sp1.use_count() << endl; // 3
sp3->use();
} // sp3, sp4 离开作用域,引用计数-2
cout << "sp1.use_count() = " << sp1.use_count() << endl; // 1
// ===== 使用 shared_ptr =====
sp1->use(); // 通过 -> 访问成员
(*sp1).use(); // 通过 * 解引用
sp1.get(); // 获取裸指针(谨慎使用)
// ===== 检查 shared_ptr =====
if (sp1) // 隐式转换为bool
cout << "sp1 非空" << endl;
// ===== reset =====
sp1.reset(); // 释放所有权,引用计数-1
cout << "reset后 sp1 为空:" << boolalpha << !sp1 << endl;
sp1.reset(new Resource("资源C")); // 重新指向新对象
cout << "sp1.use_count() = " << sp1.use_count() << endl; // 1
// ===== shared_ptr 在容器中 =====
vector<shared_ptr<Resource>> resources;
resources.push_back(make_shared<Resource>("资源D"));
resources.push_back(make_shared<Resource>("资源E"));
resources.push_back(sp1); // sp1 和 resources[2] 共享资源C
cout << "\n资源C引用计数:" << sp1.use_count() << endl; // 2
cout << "\n程序结束,所有资源自动释放:" << endl;
return 0;
}
12.1.4 shared_ptr 与 new
// shared_ptr_new.cpp -- shared_ptr与new的配合
#include <iostream>
#include <memory>
#include <string>
int main()
{
using namespace std;
// ===== make_shared vs new =====
// make_shared:一次内存分配(对象+控制块)
auto sp1 = make_shared<int>(42);
// new:两次内存分配(对象 + 控制块分开)
shared_ptr<int> sp2(new int(42));
// ===== 不要混用裸指针和 shared_ptr =====
int* raw = new int(42);
shared_ptr<int> sp3(raw);
// shared_ptr<int> sp4(raw); // ❌ 危险!两个独立的引用计数
// 当 sp3 和 sp4 都析构时,raw 会被释放两次!
// ===== 不要用 get() 初始化另一个 shared_ptr =====
auto sp5 = make_shared<int>(42);
// shared_ptr<int> sp6(sp5.get()); // ❌ 危险!独立的引用计数
// ===== 正确的共享方式:拷贝 shared_ptr =====
auto sp7 = sp5; // ✅ 正确:共享引用计数
// ===== 自定义删除器 =====
// 用于管理非内存资源(如文件句柄)
auto fileDeleter = [](FILE* f) {
if (f) { fclose(f); cout << "文件已关闭" << endl; }
};
shared_ptr<FILE> filePtr(fopen("test.txt", "w"), fileDeleter);
if (filePtr)
fputs("Hello, World!\n", filePtr.get());
// filePtr 离开作用域时自动调用 fileDeleter
return 0;
}
12.1.5 shared_ptr 的循环引用问题
// circular_reference.cpp -- 循环引用问题
#include <iostream>
#include <memory>
#include <string>
struct Node
{
string name;
shared_ptr<Node> next; // ⚠️ 可能导致循环引用
weak_ptr<Node> prev; // ✅ 使用 weak_ptr 打破循环
Node(const string& n) : name(n)
{
cout << "[创建] " << name << endl;
}
~Node()
{
cout << "[销毁] " << name << endl;
}
};
int main()
{
using namespace std;
// ===== 循环引用导致内存泄漏 =====
{
auto a = make_shared<Node>("A");
auto b = make_shared<Node>("B");
// 如果使用 shared_ptr:
// a->next = b; // a 持有 b
// b->next = a; // b 持有 a → 循环引用!
// 离开作用域时,a 和 b 的引用计数都是 1,不会释放!
// ✅ 使用 weak_ptr 打破循环
a->next = b; // a 持有 b(shared_ptr)
b->prev = a; // b 弱引用 a(weak_ptr,不增加引用计数)
cout << "a.use_count() = " << a.use_count() << endl; // 1
cout << "b.use_count() = " << b.use_count() << endl; // 2(a->next持有)
}
// a 和 b 都正确销毁
return 0;
}
12.2 unique_ptr
12.2.1 unique_ptr 基础
// unique_ptr_demo.cpp -- unique_ptr详解
#include <iostream>
#include <memory>
#include <string>
#include <vector>
class Widget
{
public:
string name;
Widget(const string& n) : name(n)
{
cout << "[创建] " << name << endl;
}
~Widget()
{
cout << "[销毁] " << name << endl;
}
void show() const { cout << "[显示] " << name << endl; }
};
int main()
{
using namespace std;
// ===== 创建 unique_ptr =====
// 方式1:make_unique(C++14,推荐)
auto up1 = make_unique<Widget>("Widget1");
// 方式2:直接构造
unique_ptr<Widget> up2(new Widget("Widget2"));
// ===== unique_ptr 独占所有权 =====
// 不能拷贝
// auto up3 = up1; // ❌ 错误!unique_ptr 不能拷贝
// 可以移动(转移所有权)
auto up3 = move(up1); // up1 变为空,up3 接管所有权
cout << "up1 为空:" << boolalpha << !up1 << endl; // true
up3->show();
// ===== 使用 unique_ptr =====
up2->show(); // 通过 -> 访问
(*up2).show(); // 通过 * 解引用
up2.get(); // 获取裸指针
// ===== release:放弃所有权,返回裸指针 =====
Widget* raw = up2.release(); // up2 变为空,raw 需要手动管理
cout << "up2 为空:" << !up2 << endl;
delete raw; // 必须手动释放!
// ===== reset:重置 =====
up3.reset(); // 释放当前对象,up3 变为空
up3.reset(new Widget("Widget3")); // 释放旧对象,指向新对象
// ===== unique_ptr 作为函数参数和返回值 =====
auto makeWidget = [](const string& name) -> unique_ptr<Widget> {
return make_unique<Widget>(name); // 移动语义,高效
};
auto w = makeWidget("Widget4");
w->show();
// ===== unique_ptr 数组 =====
auto arr = make_unique<int[]>(5); // 分配5个int的数组
for (int i = 0; i < 5; i++)
arr[i] = i * 10;
cout << "arr[2] = " << arr[2] << endl;
cout << "\n程序结束:" << endl;
return 0;
}
12.2.2 unique_ptr 的自定义删除器
// unique_ptr_deleter.cpp -- unique_ptr自定义删除器
#include <iostream>
#include <memory>
#include <cstdio>
// 自定义删除器(函数)
void closeFile(FILE* f)
{
if (f)
{
fclose(f);
std::cout << "文件已关闭" << std::endl;
}
}
int main()
{
using namespace std;
// 使用函数作为删除器
// 注意:删除器类型是 unique_ptr 的模板参数
unique_ptr<FILE, decltype(&closeFile)> fp(fopen("test.txt", "w"),
closeFile);
if (fp)
fputs("Hello!\n", fp.get());
// fp 离开作用域时自动调用 closeFile
// 使用 lambda 作为删除器
auto deleter = [](int* p) {
cout << "自定义删除:" << *p << endl;
delete p;
};
unique_ptr<int, decltype(deleter)> up(new int(42), deleter);
cout << "*up = " << *up << endl;
return 0;
}
12.3 weak_ptr
// weak_ptr_demo.cpp -- weak_ptr详解
#include <iostream>
#include <memory>
#include <string>
class Cache
{
private:
// 使用 weak_ptr 缓存,不影响对象的生命周期
weak_ptr<string> cached_;
public:
shared_ptr<string> get(const string& key)
{
// 尝试从缓存获取
auto sp = cached_.lock(); // 尝试获取 shared_ptr
if (sp)
{
cout << "缓存命中:" << *sp << endl;
return sp;
}
// 缓存未命中,创建新对象
cout << "缓存未命中,创建新对象" << endl;
sp = make_shared<string>(key + "_value");
cached_ = sp; // 存入缓存(weak_ptr)
return sp;
}
};
int main()
{
using namespace std;
// ===== weak_ptr 基础 =====
auto sp = make_shared<int>(42);
weak_ptr<int> wp = sp; // 不增加引用计数
cout << "sp.use_count() = " << sp.use_count() << endl; // 1(wp不计入)
// ===== 使用 weak_ptr =====
// 不能直接解引用 weak_ptr,必须先 lock()
if (auto locked = wp.lock()) // 成功时返回 shared_ptr
{
cout << "*locked = " << *locked << endl;
}
// ===== 检查 weak_ptr 是否有效 =====
cout << "wp.expired() = " << boolalpha << wp.expired() << endl; // false
sp.reset(); // 释放 shared_ptr
cout << "sp释放后:" << endl;
cout << "wp.expired() = " << wp.expired() << endl; // true
if (auto locked = wp.lock())
cout << "获取成功" << endl;
else
cout << "对象已销毁,获取失败" << endl;
// ===== 实际应用:缓存 =====
cout << "\n===== 缓存示例 =====" << endl;
Cache cache;
{
auto v1 = cache.get("key1"); // 创建新对象
auto v2 = cache.get("key1"); // 缓存命中
cout << "v1.use_count() = " << v1.use_count() << endl; // 2
} // v1, v2 离开作用域,对象销毁
auto v3 = cache.get("key1"); // 缓存失效,重新创建
return 0;
}
12.4 动态数组
12.4.1 new 和 delete 数组
// dynamic_array.cpp -- 动态数组
#include <iostream>
#include <string>
#include <memory>
int main()
{
using namespace std;
// ===== 分配动态数组 =====
int n = 5;
int* arr = new int[n]; // 分配n个int
// 初始化
for (int i = 0; i < n; i++)
arr[i] = i * 10;
// 使用
for (int i = 0; i < n; i++)
cout << arr[i] << " ";
cout << endl;
// 释放(必须用 delete[])
delete[] arr; // ⚠️ 不能用 delete arr!
// ===== 值初始化 =====
int* zeros = new int[5](); // 所有元素初始化为0
int* ones = new int[5]{1, 2, 3, 4, 5}; // 列表初始化
cout << "zeros: ";
for (int i = 0; i < 5; i++) cout << zeros[i] << " ";
cout << endl;
cout << "ones: ";
for (int i = 0; i < 5; i++) cout << ones[i] << " ";
cout << endl;
delete[] zeros;
delete[] ones;
// ===== 动态分配大小为0的数组 =====
// 合法,但不能解引用
int* empty = new int[0];
delete[] empty; // 必须释放
// ===== 使用 unique_ptr 管理动态数组 =====
auto uarr = make_unique<int[]>(5);
for (int i = 0; i < 5; i++)
uarr[i] = i * i;
cout << "unique_ptr数组: ";
for (int i = 0; i < 5; i++) cout << uarr[i] << " ";
cout << endl;
// 自动释放,无需 delete[]
// ===== shared_ptr 管理动态数组(需要自定义删除器)=====
shared_ptr<int> sarr(new int[5], [](int* p) { delete[] p; });
// C++17:shared_ptr<int[]> sarr(new int[5]);
return 0;
}
12.4.2 allocator 类
// allocator_demo.cpp -- allocator类
#include <iostream>
#include <memory>
#include <string>
#include <algorithm>
int main()
{
using namespace std;
// allocator:将内存分配和对象构造分离
// 适用于需要分配大块内存但延迟构造对象的场景
allocator<string> alloc;
// ===== 分配内存(不构造对象)=====
int n = 5;
string* p = alloc.allocate(n); // 分配n个string的内存
// ===== 构造对象 =====
string* q = p;
alloc.construct(q++, "Hello"); // 在p[0]构造"Hello"
alloc.construct(q++, "World"); // 在p[1]构造"World"
alloc.construct(q++, 3, 'X'); // 在p[2]构造"XXX"
cout << "构造的对象:" << endl;
for (string* it = p; it != q; ++it)
cout << " " << *it << endl;
// ===== 销毁对象(不释放内存)=====
while (q != p)
alloc.destroy(--q); // 调用析构函数
// ===== 释放内存 =====
alloc.deallocate(p, n);
// ===== 拷贝和填充未初始化内存 =====
vector<string> words = {"Hello", "World", "C++", "STL"};
// 分配足够的内存
string* buf = alloc.allocate(words.size() * 2);
// uninitialized_copy:拷贝到未初始化内存
auto end1 = uninitialized_copy(words.begin(), words.end(), buf);
// uninitialized_fill_n:填充未初始化内存
uninitialized_fill_n(end1, words.size(), string("填充"));
cout << "\n拷贝和填充后:" << endl;
for (size_t i = 0; i < words.size() * 2; i++)
cout << " " << buf[i] << endl;
// 销毁并释放
for (size_t i = 0; i < words.size() * 2; i++)
alloc.destroy(buf + i);
alloc.deallocate(buf, words.size() * 2);
return 0;
}
12.5 综合示例:内存安全的对象池
// object_pool.cpp -- 综合示例:对象池
#include <iostream>
#include <memory>
#include <vector>
#include <string>
#include <functional>
#include <stdexcept>
// 使用智能指针实现的对象池
template <typename T>
class ObjectPool
{
private:
// 可用对象列表
vector<unique_ptr<T>> available_;
// 已分配对象(使用 weak_ptr 跟踪)
vector<weak_ptr<T>> inUse_;
// 工厂函数
function<unique_ptr<T>()> factory_;
size_t maxSize_;
size_t created_ = 0;
public:
ObjectPool(function<unique_ptr<T>()> factory, size_t maxSize = 10)
: factory_(factory), maxSize_(maxSize) {}
// 获取对象(返回 shared_ptr,自动归还)
shared_ptr<T> acquire()
{
T* rawPtr = nullptr;
if (!available_.empty())
{
// 从可用列表取出
auto up = move(available_.back());
available_.pop_back();
rawPtr = up.release();
cout << "[复用] 对象" << endl;
}
else if (created_ < maxSize_)
{
// 创建新对象
auto up = factory_();
rawPtr = up.release();
created_++;
cout << "[创建] 对象(总数:" << created_ << ")" << endl;
}
else
{
throw runtime_error("对象池已满");
}
// 创建 shared_ptr,自定义删除器将对象归还池中
auto self = this;
auto deleter = [self](T* p) {
cout << "[归还] 对象" << endl;
self->available_.push_back(unique_ptr<T>(p));
};
auto sp = shared_ptr<T>(rawPtr, deleter);
inUse_.push_back(sp);
return sp;
}
size_t availableCount() const { return available_.size(); }
size_t createdCount() const { return created_; }
// 清理已失效的 weak_ptr
void cleanup()
{
inUse_.erase(
remove_if(inUse_.begin(), inUse_.end(),
[](const weak_ptr<T>& wp) { return wp.expired(); }),
inUse_.end());
}
};
// 数据库连接(模拟)
class DBConnection
{
private:
static int nextId;
int id_;
public:
DBConnection() : id_(++nextId)
{
cout << " 连接 #" << id_ << " 已建立" << endl;
}
~DBConnection()
{
cout << " 连接 #" << id_ << " 已关闭" << endl;
}
void query(const string& sql) const
{
cout << " [连接#" << id_ << "] 执行:" << sql << endl;
}
int getId() const { return id_; }
};
int DBConnection::nextId = 0;
int main()
{
using namespace std;
// 创建连接池(最多3个连接)
ObjectPool<DBConnection> pool(
[]() { return make_unique<DBConnection>(); },
3
);
cout << "===== 获取连接 =====" << endl;
{
auto conn1 = pool.acquire();
conn1->query("SELECT * FROM users");
auto conn2 = pool.acquire();
conn2->query("INSERT INTO orders VALUES (...)");
cout << "\n可用连接:" << pool.availableCount() << endl;
cout << "已创建:" << pool.createdCount() << endl;
} // conn1, conn2 自动归还
cout << "\n===== 连接归还后 =====" << endl;
cout << "可用连接:" << pool.availableCount() << endl;
cout << "\n===== 复用连接 =====" << endl;
{
auto conn3 = pool.acquire(); // 复用之前的连接
conn3->query("UPDATE products SET price = 99");
auto conn4 = pool.acquire(); // 复用
auto conn5 = pool.acquire(); // 新建
cout << "\n可用连接:" << pool.availableCount() << endl;
// 尝试超出限制
try
{
auto conn6 = pool.acquire(); // 超出限制!
}
catch (const runtime_error& e)
{
cout << "错误:" << e.what() << endl;
}
}
cout << "\n===== 所有连接归还 =====" << endl;
cout << "可用连接:" << pool.availableCount() << endl;
return 0;
}
12.6 智能指针使用规范
// smart_ptr_guidelines.cpp -- 智能指针使用规范
#include <iostream>
#include <memory>
#include <string>
class Widget
{
public:
string name;
Widget(const string& n) : name(n) {}
~Widget() { std::cout << "销毁 " << name << std::endl; }
};
// ===== 规范1:优先使用 make_shared 和 make_unique =====
void rule1()
{
// ✅ 推荐
auto sp = make_shared<Widget>("A");
auto up = make_unique<Widget>("B");
// ❌ 不推荐(两次内存分配,异常不安全)
shared_ptr<Widget> sp2(new Widget("C"));
}
// ===== 规范2:不要用裸指针初始化多个智能指针 =====
void rule2()
{
Widget* raw = new Widget("D");
shared_ptr<Widget> sp1(raw);
// shared_ptr<Widget> sp2(raw); // ❌ 危险!双重释放
// ✅ 正确:拷贝 shared_ptr
auto sp3 = sp1;
}
// ===== 规范3:不要保存 get() 返回的裸指针 =====
void rule3()
{
auto sp = make_shared<Widget>("E");
Widget* raw = sp.get(); // 获取裸指针
// ❌ 不要用 raw 初始化另一个智能指针
// shared_ptr<Widget> sp2(raw);
// ❌ 不要 delete raw
// delete raw;
// ✅ raw 只用于临时访问
std::cout << raw->name << std::endl;
}
// ===== 规范4:选择合适的智能指针 =====
// unique_ptr:独占所有权,零开销
// shared_ptr:共享所有权,有引用计数开销
// weak_ptr:观察者,不影响生命周期
// ===== 规范5:函数参数传递规范 =====
// 传递 shared_ptr:表示共享所有权
void takeOwnership(shared_ptr<Widget> sp) { sp->name; }
// 传递引用:不转移所有权,只使用
void useWidget(const Widget& w) { std::cout << w.name << std::endl; }
// 传递 unique_ptr:转移所有权
void transferOwnership(unique_ptr<Widget> up) { up->name; }
int main()
{
using namespace std;
rule1();
rule2();
rule3();
auto sp = make_shared<Widget>("F");
takeOwnership(sp); // 共享所有权
useWidget(*sp); // 只使用,不转移
auto up = make_unique<Widget>("G");
transferOwnership(move(up)); // 转移所有权
return 0;
}
📝 第12章知识点总结
| 知识点 |
核心要点 |
| 动态内存问题 |
内存泄漏、重复释放、悬空指针、异常安全问题 |
| shared_ptr |
共享所有权,引用计数,计数为0时自动释放 |
| make_shared |
推荐方式,一次内存分配(对象+控制块),更安全高效 |
| 引用计数 |
use_count() 查询,拷贝+1,析构-1,为0时释放 |
| 循环引用 |
shared_ptr 循环引用导致内存泄漏,用 weak_ptr 打破 |
| unique_ptr |
独占所有权,不能拷贝,可以移动,零开销 |
| make_unique |
C++14,推荐方式,异常安全 |
| release() |
放弃所有权,返回裸指针,需手动管理 |
| reset() |
释放当前对象,可选择指向新对象 |
| weak_ptr |
弱引用,不增加引用计数,lock() 获取 shared_ptr |
| expired() |
检查 weak_ptr 是否有效(对象是否已销毁) |
| 动态数组 |
new T[n] 分配,delete[] p 释放,make_unique<T[]>(n) 更安全 |
| allocator |
分离内存分配和对象构造,用于高性能场景 |
| 自定义删除器 |
shared_ptr 和 unique_ptr 都支持,用于管理非内存资源 |
| 使用规范 |
优先 make_shared/make_unique,不混用裸指针,不保存 get() 结果 |