C/C++语言因为能够使用指针更方便的操作内存空间,因此速度相较于其它语言更快。但是也带来了该语言的致命缺陷——内存泄露。智能指针是C++11中提出的概念,采用了RALL(Resource Acquisition Is Initialization,资源分配即初始化)的设计思想,主要是为了解决C++代码中的内存泄露问题。目前使用的智能指针有三种,分别是unique_ptr、shared_ptr和weak_ptr。使用智能指针之前,需要添加头文#include <memory>
。
一、raw_ptr
原始指针,也称为裸指针,语法如下:
Obj* obj = new Obj();
obj->doSomething();
delete obj;
obj = nullptr;
在使用new创建一个指针对象后,当不再使用该对象之后,需要及时delete。否则该块堆内存被一直占用,导致资源浪费,甚至造车给程序崩溃。
智能指针采用RALL设计思想,使用模板,对裸指针进行了封装,时智能指针在离开自己的作用域范围时能够自己调用析构函数,释放裸指针。
二、unique_ptr
独占所有权:unique_ptr称为独占智能指针,顾名思义,一旦该指针被初始化指向某对象,该对象就不允许再被其它指针获取。智能通过移动语义来转移对象的所有权。
特点:与裸指针在性能上差不多,离开作用域时自动调用
delete
或自定义的删除器。
为了实现智能指针,需要禁用拷贝构造函数和拷贝赋值函数。实现如下:
#include <iostream>
template <typename T>
class my_unique_ptr
{
private:
T* raw_ptr;
public:
explicit my_unique_ptr(T* ptr = nullptr) : raw_ptr(ptr) {}
~my_unique_ptr() {
delete raw_ptr;
}
// 禁止拷贝
my_unique_ptr(const my_unique_ptr&) = delete;
my_unique_ptr& operator=(const my_unique_ptr&) = delete;
// 移动语义
my_unique_ptr(my_unique_ptr&& other) : raw_ptr(other.raw_ptr) {
other.raw_ptr = nullptr;
}
my_unique_ptr& operator=(my_unique_ptr&& other) {
if (this != &other) {
delete raw_ptr;
raw_ptr = other.raw_ptr;
other.raw_ptr = nullptr;
}
return *this; // 添加返回语句
}
// 解引用操作
T& operator*() const { return *raw_ptr; }
T* operator->() const { return raw_ptr; }
// get
T* get() const {
return raw_ptr;
}
// release but do not release memory
T* release() {
T* temp = raw_ptr;
raw_ptr = nullptr;
return temp;
}
// reset
void reset(T* ptr = nullptr) {
delete raw_ptr;
raw_ptr = ptr; // 修正参数名称
}
};
三、shared_ptr
共享所有权:通过引用计数跟踪有多少个
shared_ptr
指向同一对象,当计数归零时自动释放内存。用途:需要多个指针共享同一资源的场景。
特点:
支持拷贝和移动语义。
线程安全(引用计数操作是原子的,但对象访问需额外同步)。
循环引用会导致内存泄漏(需配合
std::weak_ptr
解决)。
什么是循环引用?在使用shared_ptr时,两个类A和B,A引用B,B同时引用A,导致引用计数不能置零,造成内存泄露。使用weak_ptr可以避免循环引用。
shared_ptr的核心是共享所有权,通过引用计数(Reference Counting)跟踪有多少个shared_ptr指向了同一个对象。当引用计数归零时,自动释放内存。
实现shared_ptr的关键有两个部分:
控制块(Control Block)
存储引用计数,强引用,shared_ptr使用
存储弱引用,weak_ptr使用
存储自定义删除器
存储原始指针,指向被管理的对象
引用计数
每次shared_ptr拷贝构造或赋值时,强引用计数加一
每次shared_ptr析构时,强引用计数减一,归零时,调用删除器释放内存。
#include <iostream>
#include <atomic>
template<typename T>
class SharedPtr; // 前向声明
template<typename T>
class WeakPtr; // 前向声明
// 控制块类
template<typename T>
class ControlBlock {
public:
T* object;
std::atomic<int> strong_count; // 使用原子类型
std::atomic<int> weak_count; // 使用原子类型
explicit ControlBlock(T* obj)
: object(obj), strong_count(1), weak_count(0) {}
~ControlBlock() {
delete object;
}
};
template<typename T>
class SharedPtr {
private:
T* ptr;
ControlBlock<T>* control;
// 自定义的删除器
void cleanup() {
if (!control) return;
if (--control->strong_count == 0) {
// 对象销毁,但保留控制块
control->object = nullptr;
if (control->weak_count == 0) {
delete control;
}
}
}
public:
explicit SharedPtr(T* p = nullptr)
: ptr(p), control(p ? new ControlBlock<T>(p) : nullptr) {}
// 拷贝构造
SharedPtr(const SharedPtr& other)
: ptr(other.ptr), control(other.control) {
if (control) control->strong_count++;
}
// 从WeakPtr构造
explicit SharedPtr(const WeakPtr<T>& weak)
: ptr(weak.ptr), control(weak.control) {
if (control) control->strong_count++;
}
// 析构
~SharedPtr() { cleanup(); }
// 其他成员函数...
};
四、weak_ptr
weak_ptr
不增加强引用计数,仅增加弱引用计数。它用于观察 shared_ptr
管理的资源而不阻止其释放。
关键点
不增加强引用计数:
weak_ptr
的拷贝/赋值不会影响对象的生命周期。通过
lock()
获取shared_ptr
:lock()
会检查强引用计数是否 >0,如果是则返回shared_ptr
(并增加强引用计数)。如果强引用计数 =0(对象已释放),返回
nullptr
。
template<typename T>
class WeakPtr {
private:
T* ptr;
ControlBlock<T>* control;
// 自定义的删除器
void cleanup() {
if (!control) return;
if (--control->weak_count == 0 && control->strong_count == 0) {
delete control;
}
}
public:
// 默认构造
WeakPtr() : ptr(nullptr), control(nullptr) {}
// 从SharedPtr构造
WeakPtr(const SharedPtr<T>& shared)
: ptr(shared.get()), control(shared.control) {
if (control) control->weak_count++;
}
// 拷贝构造
WeakPtr(const WeakPtr& other)
: ptr(other.ptr), control(other.control) {
if (control) control->weak_count++;
}
// 析构
~WeakPtr() { cleanup(); }
// 转换为SharedPtr
SharedPtr<T> lock() const {
return control && control->strong_count > 0
? SharedPtr<T>(*this)
: SharedPtr<T>();
}
// 其他成员函数...
};