跳到主要内容

C++ 智能指针

智能指针是 C++11 引入的 RAII 资源管理机制,自动管理动态分配的内存,避免内存泄漏和悬空指针问题。

为什么需要智能指针?

传统内存管理的问题

void process() {
// 问题1:忘记释放内存
int* p1 = new int(10);
// ... 忘记 delete

// 问题2:异常导致内存泄漏
int* p2 = new int(20);
if (some_condition) {
throw std::runtime_error("error"); // p2 泄漏
}
delete p2;

// 问题3:悬空指针
int* p3 = new int(30);
delete p3;
// p3 已成为悬空指针,使用会导致未定义行为
// *p3 = 100; // 危险!

// 问题4:重复释放
int* p4 = new int(40);
delete p4;
delete p4; // 未定义行为
}

RAII 思想

RAII(Resource Acquisition Is Initialization)是一种资源管理策略:

class Resource {
private:
int* data;

public:
Resource() {
data = new int(100);
std::cout << "资源获取" << std::endl;
}

~Resource() {
delete data;
std::cout << "资源释放" << std::endl;
}
};

void process() {
Resource r; // 构造函数获取资源
// 使用 r...
// 离开作用域时析构函数自动释放资源
} // 自动调用析构函数

unique_ptr

独占所有权的智能指针

unique_ptr 独占对象的所有权,同一时间只能有一个 unique_ptr 指向对象:

#include <memory>

int main() {
// 创建 unique_ptr
std::unique_ptr<int> p1(new int(10));
std::unique_ptr<int> p2 = std::make_unique<int>(20); // C++14 推荐

// 使用
*p1 = 100;
std::cout << *p1 << std::endl;

// 移动(独占权转移)
std::unique_ptr<int> p3 = std::move(p1); // p1 变为空

// 检查是否拥有对象
if (p2) {
std::cout << "p2 拥有对象" << std::endl;
}

// 释放所有权(删除对象)
p2.reset(); // 删除对象,p2 变为空
p2.reset(new int(30)); // 删除旧对象,指向新对象

// 获取原始指针
int* raw = p3.get();

// 离开作用域时自动删除对象
return 0;
}

自定义删除器

#include <memory>
#include <fstream>

int main() {
// 使用自定义删除器
auto deleter = [](FILE* f) {
if (f) {
fclose(f);
std::cout << "文件已关闭" << std::endl;
}
};

std::unique_ptr<FILE, decltype(deleter)>
file(fopen("test.txt", "w"), deleter);

// 使用文件...
// 关闭时自动调用 deleter

return 0;
}

数组特化

int main() {
// unique_ptr 数组
std::unique_ptr<int[]> arr(new int[10]);
arr[0] = 1;
arr[9] = 10;

// C++20 推荐使用 std::make_unique_for_overwrite
// auto arr2 = std::make_unique_for_overwrite<int[]>(10);

return 0;
}

shared_ptr

共享所有权的智能指针

shared_ptr 通过引用计数共享对象的所有权:

#include <memory>

int main() {
// 创建 shared_ptr
std::shared_ptr<int> p1(new int(10));
std::shared_ptr<int> p2 = std::make_shared<int>(20); // 推荐

// 引用计数
std::cout << p1.use_count() << std::endl; // 1

std::shared_ptr<int> p3 = p1; // 复制,引用计数变为 2
std::cout << p1.use_count() << std::endl; // 2

// 交换
p1.swap(p2);

// 释放
p3.reset(); // 引用计数变为 1

// 检查
if (p1) {
std::cout << *p1 << std::endl;
}

// 获取原始指针
int* raw = p1.get();

return 0;
}

循环引用问题

#include <memory>
#include <iostream>

class Node {
public:
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev;
int value;

Node(int v) : value(v) {
std::cout << "Node " << value << " constructed" << std::endl;
}

~Node() {
std::cout << "Node " << value << " destructed" << std::endl;
}
};

int main() {
// 循环引用导致内存泄漏!
auto p1 = std::make_shared<Node>(1);
auto p2 = std::make_shared<Node>(2);

p1->next = p2; // p2 引用计数 +1
p2->prev = p1; // p1 引用计数 +1

// 循环引用:
// p1 持有 p2,p2 持有 p1
// 外部只有 p1 和 p2 两个 shared_ptr
// 它们被销毁时,引用计数都是 1,无法释放

return 0;
// 输出显示没有调用析构函数!
}

weak_ptr 打破循环引用

#include <memory>
#include <iostream>

class Node {
public:
std::weak_ptr<Node> next; // 使用 weak_ptr
std::weak_ptr<Node> prev;
int value;

Node(int v) : value(v) {
std::cout << "Node " << value << " constructed" << std::endl;
}

~Node() {
std::cout << "Node " << value << " destructed" << std::endl;
}
};

int main() {
auto p1 = std::make_shared<Node>(1);
auto p2 = std::make_shared<Node>(2);

p1->next = p2;
p2->prev = p1;

// 使用 weak_ptr
if (auto next = p1->next.lock()) { // 尝试升级为 shared_ptr
std::cout << "Next value: " << next->value << std::endl;
}

return 0;
// 正常析构!
}

weak_ptr

弱引用的智能指针

weak_ptr 是一种不增加引用计数的智能指针,用于观察 shared_ptr 管理的对象:

#include <memory>

int main() {
std::shared_ptr<int> sp = std::make_shared<int>(100);
std::weak_ptr<int> wp = sp; // 不增加引用计数

// 检查对象是否存在
if (!wp.expired()) {
std::cout << "对象仍存在" << std::endl;
}

// 尝试获取 shared_ptr
if (auto sp2 = wp.lock()) { // 返回 shared_ptr 或空
std::cout << *sp2 << std::endl;
}

// 重置
wp.reset();

return 0;
}

典型应用场景

#include <memory>
#include <iostream>

class Subject {
private:
std::vector<std::weak_ptr<Observer>> observers;

public:
void attach(std::shared_ptr<Observer> obs) {
observers.push_back(obs);
}

void notify() {
for (auto it = observers.begin(); it != observers.end(); ) {
if (auto obs = it->lock()) {
obs->onNotify();
++it;
} else {
// 观察者已销毁,移除弱引用
it = observers.erase(it);
}
}
}
};

智能指针的陷阱

1. 循环引用

使用 weak_ptr 打破循环引用。

2. 原始指针与智能指针混用

// 错误:双重释放
int* p = new int(10);
std::shared_ptr<int> sp1(p);
std::shared_ptr<int> sp2(p); // 危险!两个独立的引用计数

// 正确
std::shared_ptr<int> sp1 = std::make_shared<int>(10);
// 不要从原始指针创建多个 shared_ptr

3. 内存泄漏

// 错误:shared_ptr 保存原始指针,创建新的引用计数
int* p = new int(10);
std::shared_ptr<int> sp(p);
// 某些情况可能导致泄漏

// 正确:使用 make_shared
auto sp = std::make_shared<int>(10);

4. this 指针

class Node : public std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> getSelf() {
return shared_from_this(); // 从 this 创建 shared_ptr
}
};

int main() {
auto node = std::make_shared<Node>();
auto self = node->getSelf(); // 正确
return 0;
}

智能指针与工厂函数

自定义删除器示例

#include <memory>
#include <iostream>

class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
void use() { std::cout << "Resource used\n"; }
};

int main() {
// 使用自定义删除器
auto customDeleter = [](Resource* r) {
std::cout << "Custom delete called\n";
delete r;
};

std::unique_ptr<Resource, decltype(customDeleter)>
p(new Resource(), customDeleter);

p->use();
// 退出作用域时调用自定义删除器

return 0;
}

工厂函数模式

#include <memory>
#include <iostream>

class Database {
public:
static std::shared_ptr<Database> create() {
// 创建连接
return std::shared_ptr<Database>(
new Database(),
[](Database* db) {
// 自定义清理:关闭连接
db->close();
delete db;
}
);
}

void query(const std::string& sql) {
std::cout << "Executing: " << sql << std::endl;
}

private:
Database() { std::cout << "Connection opened\n"; }
void close() { std::cout << "Connection closed\n"; }
};

int main() {
auto db = Database::create();
db->query("SELECT * FROM users");
return 0;
}

现代 C++ 内存管理建议

优先使用智能指针

场景推荐
独占所有权unique_ptr
共享所有权shared_ptr
观察但不拥有weak_ptr

避免

// 避免
int* p = new int;
delete p;

// 避免
newdelete 混用

// 避免
裸指针作为成员变量

推荐

// 推荐:make_unique (C++14)
auto p = std::make_unique<int>(10);

// 推荐:make_shared
auto sp = std::make_shared<int>(10);

// 推荐:容器存储智能指针
std::vector<std::unique_ptr<int>> vec;
vec.push_back(std::make_unique<int>(1));

// 推荐:函数参数使用引用或智能指针
void process(std::shared_ptr<const Data> data);
void process(const Data& data); // 更轻量

练习

  1. 使用 unique_ptr 实现一个资源管理器
  2. 使用 shared_ptr 和 weak_ptr 实现观察者模式
  3. 编写一个线程安全的 shared_ptr 工厂函数
  4. 分析代码中的内存泄漏问题并修复

小结

本章学习了:

  1. 为什么需要智能指针:解决内存管理问题
  2. unique_ptr:独占所有权的智能指针
  3. shared_ptr:共享所有权的智能指针
  4. weak_ptr:不增加引用计数的观察者
  5. 常见陷阱:循环引用、原始指针混用等

下一步

接下来让我们学习 C++ 多线程编程,了解并发编程。