智能指针
智能指针概述
智能指针是C++中一种用于自动管理动态分配内存的指针类型,它通过封装原始指针并提供自动内存释放机制,有效避免了内存泄漏和悬垂指针(野指针)问题。智能指针利用了C++的RAII(Resource Acquisition Is Initialization,资源获取即初始化)原则,确保在对象生命周期结束时自动释放其管理的资源。
智能指针的类型及特点
在C++标准库中,主要有以下几种智能指针:
std::unique_ptr
所有权模型:独占所有权(Exclusive Ownership)。某个时刻只能有一个 unique_ptr 指向同一个对象。
特点:
轻量级、零额外开销(与裸指针相比,几乎无运行时开销)。
不可复制(Copy),但支持移动语义(Move),所有权可以通过移动操作(std::move)转移。
非常适合用作工厂函数的返回值或管理单所有者的资源。
支持数组(std::unique_ptr<T[]>,会对每个元素调用 delete[])。
可以方便地设置自定义删除器。
典型使用场景:
替代 new 创建的对象,确保异常安全。
类内部成员(拥有独占权)。
需要传递对象所有权(使用 std::move)。
管理数组。
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed\n"; }
~MyClass() { std::cout << "MyClass destroyed\n"; }
};
int main() {
std::unique_ptr<MyClass> ptr1(new MyClass()); // 独占所有权
// std::unique_ptr<MyClass> ptr2 = ptr1; // 错误:不能复制
std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // 转移所有权
return 0; // ptr2离开作用域,自动释放MyClass对象
}
std::shared_ptr
所有权模型:共享所有权(Shared Ownership)。多个 shared_ptr 可以指向同一个对象。
实现机制:使用引用计数(Reference Counting)。每个 shared_ptr 都有一个关联的控制块(包含引用计数、弱引用计数、删除器、分配器等)。拷贝构造/赋值时引用计数增加;析构时引用计数减少;当引用计数降为 0 时,对象被删除,控制块本身也可能被释放。
特点:
允许资源被多个拥有者共享。
比 unique_ptr 稍重(有控制块开销)。
线程安全仅限于控制块(引用计数的增减是原子的🌐,对象本身的操作仍需用户保证线程安全)。
支持 weak_ptr。
也支持自定义删除器。
典型使用场景:
多个实体需要共享访问同一个对象。
对象需要在多个地方管理生命周期。
存在复杂的关联关系。
需要实现缓存或持有者不强制对象保持存活的情况(搭配 weak_ptr)。
重要提示:
避免用同一个原生指针创建多个独立的 shared_ptr:这会导致多重释放。shared_ptr 共享所有权时,总是应该通过拷贝现有的 shared_ptr,或者使用 std::make_shared。
小心循环引用:两个或多个 shared_ptr 互相持有(如双向链表、树形结构父节点持有子节点但子节点也持有指向父节点的 shared_ptr),导致引用计数无法归零,内存泄漏。解决方案:用 weak_ptr 打破循环。
#include <memory>
#include <iostream>
class MyClass {
public:
MyClass() { std::cout << "MyClass constructed\n"; }
~MyClass() { std::cout << "MyClass destroyed\n"; }
};
int main() {
std::shared_ptr<MyClass> ptr1(new MyClass()); // 引用计数为1
{
std::shared_ptr<MyClass> ptr2 = ptr1; // 引用计数增加到2
} // ptr2离开作用域,引用计数减少到1
return 0; // ptr1离开作用域,引用计数减少到0,自动释放MyClass对象
}
std::weak_ptr
所有权模型:不拥有所有权(No Ownership)。它是一个弱引用、观察者。不增加 shared_ptr 的引用计数。
用途:
打破 shared_ptr 可能产生的循环引用,避免内存泄漏。将可能存在循环引用的成员定义为 weak_ptr。
提供对 shared_ptr 管理对象的临时访问,但需要保证访问时对象仍然存在。
特点:
不能直接解引用操作符 -> 或 * 访问对象。
需要通过 lock() 成员函数尝试提升(promote)为一个临时的 shared_ptr。如果原始对象已被销毁(expired() 为 true),则 lock() 返回一个空的 shared_ptr。
构造时需要用 shared_ptr 或另一个 weak_ptr 初始化。
典型使用场景:
解决循环引用问题(如观察者模式中被观察者持有观察者的 weak_ptr)。
缓存系统(保留弱引用,对象可能被移除)。
需要访问对象但不希望影响其生命周期时。
#include <memory>
void exampleWeak() {
std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
std::weak_ptr<MyClass> weakPtr = sharedPtr; // 弱引用,不增加计数
// ...
// 尝试访问
if (auto tempShared = weakPtr.lock()) { // 如果对象存在,获得一个有效的 shared_ptr
tempShared->doSomething(); // 安全操作
} else {
// 对象已被销毁
}
// weakPtr 离开作用域不影响 sharedPtr 的计数
}
std::auto_ptr (已废弃 C++17 中移除,C++11 起弃用)
所有权模型:类似 unique_ptr 的独占所有权。
问题:
尝试在对象被复制(通过拷贝构造或赋值)时转移所有权,但这在标准容器中使用或某些场景下会导致意外的所有权转移和潜在的空悬指针。
其行为不符合直觉(如传递给函数可能导致所有权意外转移)并容易出错。
替代:绝对不要使用 auto_ptr。请始终使用 unique_ptr,它不仅提供了更安全的独占语义,还支持移动语义而不易出错。
为什么使用智能指针?(核心优势)
防止内存泄漏:确保 new 分配的内存在不再需要时被 delete(或通过自定义删除器清理)。
防止空悬指针:在对象被删除后,所有相关的智能指针(尤其是 unique_ptr 和赋值后的 shared_ptr)将指向一个已知状态(通常为空),防止意外访问已释放的内存。
防止重复释放:通过明确的、自动化的所有权管理(unique_ptr 保证单一所有者;shared_ptr 的引用计数保证只删一次)。
自动释放资源:不仅限于内存,可通过定制删除器自动释放文件、套接字、数据库连接等。
增强异常安全性:在函数内部分配资源后,即使后续代码抛出异常,智能指针的析构也会确保资源被正确清理。
清晰的代码意图:使用 unique_ptr 表明独占权;使用 shared_ptr 表明共享所有权;使用 weak_ptr 表明弱引用或无影响所有权的访问意图。