跳到主要内容

C++智能指针完全指南

一、智能指针概述

1.1 为什么需要智能指针

C++98的问题

// ❌ 手动内存管理,容易出错
void process() {
MyClass* obj = new MyClass();

try {
obj->doWork();
// ...复杂逻辑
delete obj; // 可能执行不到
} catch (const std::exception& e) {
delete obj; // 需要手动清理
throw;
}
}

智能指针的优势

// ✅ 自动管理,异常安全
#include <memory>

void process() {
auto obj = std::make_unique<MyClass>();
obj->doWork();
// 自动释放,无需手动delete
}

1.2 智能指针类型对比

类型所有权引用计数使用场景C++标准
unique_ptr独占单一所有者C++11
shared_ptr共享多个所有者C++11
weak_ptr弱引用不增加观察shared_ptrC++11

1.3 内存管理对比

// ❌ 原始指针
MyClass* p1 = new MyClass();
MyClass* p2 = p1; // 谁负责delete?
delete p1; // p2变成悬空指针

// ✅ unique_ptr
auto p1 = std::make_unique<MyClass>();
// auto p2 = p1; // 编译错误,无法复制

// ✅ shared_ptr
auto p1 = std::make_shared<MyClass>();
auto p2 = p1; // 引用计数+1
// 自动管理内存

二、unique_ptr详解

2.1 基本用法

创建unique_ptr

#include <memory>

// 方式1:make_unique (C++14)
auto ptr1 = std::make_unique<MyClass>();

// 方式2:new表达式
auto ptr2 = std::unique_ptr<MyClass>(new MyClass());

// 方式3:自定义删除器
auto deleter = [](MyClass* p) {
std::cout << "Deleting..." << std::endl;
delete p;
};
auto ptr3 = std::unique_ptr<MyClass, decltype(deleter)>(
new MyClass(), deleter);

2.2 所有权转移

// 移动语义
std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>();

// ✅ 移动所有权
std::unique_ptr<MyClass> ptr2 = std::move(ptr1);
// ptr1现在为nullptr
assert(ptr1 == nullptr);
assert(ptr2 != nullptr);

// ❌ 无法复制
// auto ptr3 = ptr2; // 编译错误

2.3 使用unique_ptr管理数组

// 管理数组
auto arr = std::make_unique<int[]>(10);

// 访问元素
for (int i = 0; i < 10; ++i) {
arr[i] = i * 2;
}

// 自定义数组删除器
auto arr2 = std::unique_ptr<int[]>(new int[10]);

2.4 实际应用场景

// 工厂函数返回unique_ptr
std::unique_ptr<MyClass> createMyClass() {
return std::make_unique<MyClass>();
}

// 使用
auto obj = createMyClass();
obj->doWork();

// 容器存储unique_ptr
std::vector<std::unique_ptr<Animal>> zoo;
zoo.push_back(std::make_unique<Dog>());
zoo.push_back(std::make_unique<Cat>());

for (const auto& animal : zoo) {
animal->speak();
}

三、shared_ptr详解

3.1 基本用法

创建shared_ptr

#include <memory>

// 方式1:make_shared (推荐)
auto ptr1 = std::make_shared<MyClass>();

// 方式2:new表达式 (效率较低)
auto ptr2 = std::shared_ptr<MyClass>(new MyClass());

// 方式3:从unique_ptr创建
std::unique_ptr<MyClass> unique = std::make_unique<MyClass>();
auto ptr3 = std::move(unique);

3.2 引用计数

// 引用计数示例
auto ptr1 = std::make_shared<MyClass>();
std::cout << "use_count: " << ptr1.use_count() << std::endl; // 1

{
auto ptr2 = ptr1;
std::cout << "use_count: " << ptr1.use_count() << std::endl; // 2
std::cout << "use_count: " << ptr2.use_count() << std::endl; // 2
}

std::cout << "use_count: " << ptr1.use_count() << std::endl; // 1

3.3 自定义删除器

// 自定义删除器
auto deleter = [](MyClass* p) {
std::cout << "Custom deleter" << std::endl;
delete p;
};

auto ptr = std::shared_ptr<MyClass>(new MyClass(), deleter);

3.4 make_shared的优势

// ❌ 使用new:两次内存分配
// 1. 分配MyClass对象
// 2. 分配控制块(引用计数等)
auto ptr1 = std::shared_ptr<MyClass>(new MyClass());

// ✅ 使用make_shared:一次内存分配
// 同时分配对象和控制块
auto ptr2 = std::make_shared<MyClass>();

四、weak_ptr详解

4.1 解决循环引用

循环引用问题

// ❌ 循环引用导致内存泄漏
class Node {
public:
std::shared_ptr<Node> next;
// ...
};

auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();

node1->next = node2;
node2->next = node1; // 循环引用,引用计数永远不会归零

使用weak_ptr解决

// ✅ 使用weak_ptr打破循环
class Node {
public:
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 弱引用
// ...
};

auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();

node1->next = node2;
node2->prev = node1; // weak_ptr不增加引用计数

4.2 weak_ptr操作

auto shared = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weak = shared;

// 检查是否过期
if (!weak.expired()) {
// 锁定获取shared_ptr
auto locked = weak.lock();
locked->doWork();
}

// 获取引用计数
std::cout << "use_count: " << weak.use_count() << std::endl;

五、最佳实践

5.1 创建智能指针

// ✅ 推荐:使用make_unique/make_shared
auto ptr = std::make_unique<MyClass>();
auto ptr2 = std::make_shared<MyClass>();

// ❌ 不推荐:直接使用new
auto ptr3 = std::unique_ptr<MyClass>(new MyClass());

5.2 函数参数

// 按值传递(增加引用计数)
void process(std::shared_ptr<MyClass> ptr);

// 按const引用传递(不增加引用计数)
void process(const std::shared_ptr<MyClass>& ptr);

// ✅ 推荐:按const引用传递,避免不必要的引用计数操作
void process(const std::shared_ptr<MyClass>& ptr) {
ptr->doWork();
}

// 对于unique_ptr,传递所有权
void takeOwnership(std::unique_ptr<MyClass> ptr);

5.3 返回值

// ✅ 返回unique_ptr:转移所有权
std::unique_ptr<MyClass> createObject() {
return std::make_unique<MyClass>();
}

// ✅ 返回shared_ptr:共享所有权
std::shared_ptr<MyClass> createShared() {
return std::make_shared<MyClass>();
}

5.4 避免混用原始指针

// ❌ 不推荐:混用原始指针和智能指针
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
MyClass* raw = ptr.get();
// raw可能在ptr销毁后失效

// ✅ 推荐:始终使用智能指针
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();
auto ptr2 = ptr; // 使用智能指针

六、实际应用案例

6.1 资源管理

// 文件管理
class File {
public:
File(const std::string& path) {
file = fopen(path.c_str(), "r");
}

~File() {
if (file) {
fclose(file);
}
}

// 禁止拷贝
File(const File&) = delete;
File& operator=(const File&) = delete;

void read() {
// 读取文件
}

private:
FILE* file;
};

// 使用unique_ptr管理
auto file = std::make_unique<File>("data.txt");
file->read();

6.2 图结构

// 图节点
class GraphNode {
public:
using NodePtr = std::shared_ptr<GraphNode>;
using WeakNodePtr = std::weak_ptr<GraphNode>;

void addNeighbor(NodePtr neighbor) {
neighbors.push_back(neighbor);
}

std::vector<NodePtr> neighbors;
WeakNodePtr parent; // 避免循环引用
};

// 构建图
auto node1 = std::make_shared<GraphNode>();
auto node2 = std::make_shared<GraphNode>();
auto node3 = std::make_shared<GraphNode>();

node1->addNeighbor(node2);
node2->addNeighbor(node3);

6.3 观察者模式

// 主题
class Subject {
public:
using ObserverPtr = std::shared_ptr<Observer>;
using WeakObserverPtr = std::weak_ptr<Observer>;

void addObserver(ObserverPtr observer) {
observers.push_back(observer);
}

void notify() {
for (auto& weakObs : observers) {
if (auto obs = weakObs.lock()) {
obs->update();
}
}
}

private:
std::vector<WeakObserverPtr> observers;
};

七、性能考虑

7.1 性能对比

操作unique_ptrshared_ptr原始指针
创建低开销1次分配零开销
复制禁止原子操作零开销
移动零开销零开销N/A
访问零开销一次间接零开销
销毁零开销原子操作零开销

7.2 优化建议

// ✅ 优先使用unique_ptr
auto ptr = std::make_unique<MyClass>();

// ✅ 需要共享时使用shared_ptr
auto shared = std::make_shared<MyClass>();

// ✅ 使用const引用传递
void func(const std::shared_ptr<MyClass>& ptr);

// ⚠️ 避免过度使用shared_ptr
// 如果不需要共享所有权,使用unique_ptr

八、常见陷阱

8.1 this指针问题

// ❌ 错误:从this创建shared_ptr
class MyClass {
public:
void registerSelf() {
// 危险:可能创建多个独立的shared_ptr
registry.push_back(std::shared_ptr<MyClass>(this));
}
};

// ✅ 正确:使用enable_shared_from_this
class MyClass : public std::enable_shared_from_this<MyClass> {
public:
void registerSelf() {
registry.push_back(shared_from_this());
}
};

8.2 数组问题

// ❌ 错误:shared_ptr默认使用delete,不是delete[]
auto ptr = std::shared_ptr<int[]>(new int[10]);

// ✅ 正确:指定删除器或使用unique_ptr
auto ptr1 = std::unique_ptr<int[]>(new int[10]);
auto ptr2 = std::shared_ptr<int>(new int[10], std::default_delete<int[]>());

8.3 交叉引用

// ❌ 交叉引用问题
class A {
std::shared_ptr<B> b;
};

class B {
std::shared_ptr<A> a;
};

// ✅ 使用weak_ptr打破交叉引用
class A {
std::shared_ptr<B> b;
};

class B {
std::weak_ptr<A> a;
};

九、总结

9.1 选择指南

需要智能指针?

所有权转移?
├─ 是 → unique_ptr
└─ 否 → 需要共享?
├─ 是 → shared_ptr
└─ 否 → 原始指针或值

9.2 核心要点

要点说明
默认选择unique_ptr
共享所有权shared_ptr
观察者weak_ptr
创建方式make_unique/make_shared
禁止拷贝unique_ptr
引用计数shared_ptr
循环引用weak_ptr解决

9.3 最佳实践

  • ✅ 优先使用make_uniquemake_shared
  • ✅ 使用unique_ptr作为默认选择
  • ✅ 需要shared_ptr时明确语义
  • ✅ 使用weak_ptr解决循环引用
  • ✅ 使用const引用传递shared_ptr
  • ❌ 避免混用原始指针
  • ❌ 避免不必要的使用shared_ptr
  • ❌ 不要用this创建shared_ptr