C++智能指针的使用
1. 智能指针的作用
C++中普通的原始指针有两个主要问题:
- 没有指针的所有权概念,导致资源管理困难
- 没有指针生命周期跟踪,可能导致内存泄漏和野指针错误
智能指针正是为了解决上述问题而设计,它在原始指针的基础上增加了所有权及生命周期管理的功能。智能指针通常实现以下功能:
- 过载indirection运算符(
*
)和member access运算符(->
),提供和原始指针一样的用法 - 负责指针指向对象的创建和释放
- 防止内存泄漏
- 当指针失效时防止野指针错误
总而言之,智能指针让原始C++指针变得更安全、简单,是一个非常有价值的现代C++技术。
2. 智能指针的类型
C++11中常用的智能指针有三种:
std::unique_ptr
:独占式拥有权的智能指针,同一时间只能有一个unique_ptr
指向一个对象。std::shared_ptr
:共享式拥有权的智能指针,多个shared_ptr
可以指向同一个对象。std::weak_ptr
:弱引用式智能指针,指向一个由shared_ptr
管理的对象。
下面逐一对其用法进行介绍。
3. std::unique_ptr的用法
std::unique_ptr实现独占式拥有权的语义,每个std::unique_ptr实例都完全控制所指向对象的生命周期,不允许其他智能指针共享所有权。主要用法有:
- 创建std::unique_ptr:
std::unique_ptr<T> ptr(new T());
- 通过get()访问原始指针:
T* rawPtr = ptr.get();
- 转移所有权:
std::unique_ptr<T> ptr2 = std::move(ptr1);
- 释放指针:
ptr.reset();
需要注意std::unique_ptr不支持复制,只能移动。
独占一个动态对象,禁止复制:
std::unique_ptr<Foo> p1(new Foo);
// 编译错误,unique_ptr不能复制
std::unique_ptr<Foo> p2 = p1;
// 正确,移动p1的所有权
std::unique_ptr<Foo> p3 = std::move(p1);
传递所有权:
void process(std::unique_ptr<Foo> ptr) {
// 操作 ptr
}
std::unique_ptr<Foo> p(new Foo);
process(std::move(p));
4. std::shared_ptr的用法
std::shared_ptr实现共享式拥有权,多个std::shared_ptr实例可以共享同一个对象。用法包括:
- 创建:
std::shared_ptr<T> ptr(new T());
- 复制:
std::shared_ptr<T> ptr2 = ptr1;
- 获取引用计数:
std::size_t count = ptr.use_count();
- 获取原始指针:
T* rawPtr = ptr.get();
使用std::shared_ptr可以避免手动调用delete释放资源。
共享对象所有权:
std::shared_ptr<Foo> p1(new Foo);
std::shared_ptr<Foo> p2 = p1; // 共享 p1
防止循环引用导致内存泄漏:
struct Node {
std::shared_ptr<Node> next;
~Node() { cout << "Destruct Node\n"; }
};
void processList(std::shared_ptr<Node> head) {
std::shared_ptr<Node> curr = head;
while(curr) {
curr = curr->next;
}
} // head引用计数自动置零,节点可以释放
5. std::weak_ptr的用法
std::weak_ptr不控制对象生命周期,仅提供对std::shared_ptr管理对象的一个弱引用。主要用法有:
- 从std::shared_ptr创建:
std::weak_ptr<T> weakPtr(sharedPtr);
- 使用expired()检查是否失效:
if(weakPtr.expired()) {...}
- lock()返回共享所有权:
std::shared_ptr<T> sharedPtr = weakPtr.lock();
std::weak_ptr可以用来解决std::shared_ptr循环引用问题。
解决shared_ptr循环引用问题:
struct Node {
std::weak_ptr<Node> next;
};
std::shared_ptr<Node> head(new Node());
head->next = head; // 不会循环引用