前言
在 C++ 编程中,动态内存管理始终是开发者面临的核心挑战之一。手动使用 new 分配内存、delete 释放内存的模式,不仅需要开发者时刻关注内存生命周期,更可能因疏忽导致内存泄漏(忘记调用 delete)、二次释放(重复调用 delete),或是在异常抛出时因执行流跳转跳过 delete 语句等问题 —— 这些隐患轻则导致程序性能退化,重则引发崩溃或不可预期的运行错误。
C++ 智能指针基于 RAII 思想自动管理动态内存,解决手动 delete 导致的泄漏和重复释放问题。标准库提供 auto_ptr、unique_ptr、shared_ptr 和 weak_ptr 四种类型。unique_ptr 独占所有权,shared_ptr 通过引用计数共享资源但需防范循环引用,weak_ptr 辅助打破循环。本文解析各类型原理及模拟实现,涵盖删除定制器等进阶用法。

在 C++ 编程中,动态内存管理始终是开发者面临的核心挑战之一。手动使用 new 分配内存、delete 释放内存的模式,不仅需要开发者时刻关注内存生命周期,更可能因疏忽导致内存泄漏(忘记调用 delete)、二次释放(重复调用 delete),或是在异常抛出时因执行流跳转跳过 delete 语句等问题 —— 这些隐患轻则导致程序性能退化,重则引发崩溃或不可预期的运行错误。
为解决这一痛点,C++ 标准库引入了智能指针这一核心工具。它基于'资源获取即初始化'(RAII)的设计思想,将动态内存资源封装为对象的成员,利用 C++ 对象自动调用析构函数的特性,实现内存的'自动释放',从根本上减少手动管理内存的负担与风险。
本文将系统梳理智能指针的核心作用、实现原理,并针对 C++ 标准库中不同类型的智能指针展开详细解析,包括它们的设计逻辑、模拟实现代码、适用场景,以及如何规避 shared_ptr 循环引用等典型问题。
如果正确使用了智能指针的话,就不用自己手动 delete 了。
自己手动 delete 容易忽略的地方:在抛异常时容易跳过自己写的 delete 或者自己忘记 delete 了。
原理:资源交给对象管理,对象生命周期内,资源有效;对象生命周期到了,释放资源——用的是 RAII 思想。
在实现的时候要让这个对象的用法像指针一样。
template <class T>
class SmartPtr {
public:
SmartPtr(T* ptr) : _ptr(ptr) {}
~SmartPtr() { delete _ptr; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
使用的示例:
SmartPtr<string> sp(new string("renshen"));
但是这种智能指针有个问题:无法处理拷贝语义,容易导致悬空指针。
不需要拷贝的场景,一般使用 unique_ptr。 需要拷贝的场景,一般使用 shared_ptr,但是要小心循环引用。
这个是 C++98 实现的,原理其实就是管理权的转移。例如在拷贝时,会把被拷贝对象的资源管理权转移给拷贝对象,被拷贝对象悬空。
这个智能指针也是基于 RAII 思想搞出来的。
这个智能指针有很大的弊端:如果管理权被转移了的话,之前那个指针就会变成空指针。因此很多公司都是明令禁止使用的。
template <class T>
class auto_ptr {
public:
auto_ptr(T* ptr) : _ptr(ptr) {}
~auto_ptr() { delete _ptr; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
// 拷贝构造函数,转移所有权
auto_ptr(auto_ptr<T>& ap) : _ptr(ap._ptr) {
ap._ptr = nullptr;
}
// 赋值运算符重载也会转移所有权
private:
T* _ptr;
};
// 使用示例
auto_ptr<A> ap1(new A(5));
unique_ptr、shared_ptr 和 weak_ptr 是 Boost 库先实现的,后来 C++11 也支持这几个智能指针的使用了。
unique_ptr 的话就是直接让智能指针不能拷贝和赋值 + 管理权转移。
这个智能指针也是基于 RAII 思想实现的。
template <class T>
class unique_ptr {
public:
unique_ptr(T* ptr) : _ptr(ptr) {}
~unique_ptr() { delete _ptr; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
// 禁用拷贝构造和赋值
unique_ptr(unique_ptr<T>& ap) = delete;
unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
private:
T* _ptr;
};
// 使用示例
unique_ptr<A> up1(new A(5));
引申:一般拷贝不让实现的话,赋值也不能实现。
这个智能指针是最全面的,一般都是用的这个,然后有循环引用的时候搭配 weak_ptr 用。
也是 RAII 思想实现的。
原理:通过引用计数的方式来实现多个 shared_ptr 对象之间共享资源。
template <class T>
class shared_ptr {
public:
shared_ptr(T* ptr = nullptr) : _ptr(ptr), _pcount(new int(1)) {}
template <class D>
shared_ptr(T* ptr, D del) : _ptr(ptr), _pcount(new int(1)), _del(del) {}
~shared_ptr() {
if (--(*_pcount) == 0) {
_del(_ptr);
delete _pcount;
}
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr), _pcount(sp._pcount) {
++(*_pcount);
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
if (_ptr == sp._ptr) return *this;
if (--(*_pcount) == 0) {
_del(_ptr);
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
return *this;
}
T* get() const { return _ptr; }
private:
T* _ptr;
int* _pcount;
function<void(T*)> _del = [](T* ptr){ delete ptr; };
};
// 用法
shared_ptr<A> sp1(new A(1));
引申:编译器对越界访问的检查一般不是很彻底的。
shared_ptr 在遇到循环引用的时候也是会内存泄漏的。
weak_ptr 这个智能指针不是 RAII 思想的,他的唯一作用就是解决 shared_ptr 的循环引用问题的。
这个智能指针可以访问资源,但是不参与资源释放的解决。
weak_ptr 不能管理资源!
注意:weak_ptr 库里面实现了让他可以和 shared_ptr 相互转换。
template <class T>
class weak_ptr {
public:
weak_ptr() : _ptr(nullptr) {}
weak_ptr(const shared_ptr<T>& sp) : _ptr(sp.get()) {}
weak_ptr<T>& operator=(const shared_ptr<T>& sp) {
_ptr = sp.get();
return *this;
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
// weak_ptr 其实也是可以转换成 shared_ptr 的,只是自己没有实现罢了
在智能指针使用过程中,可能会有 new[] 或者 malloc 出来的空间,这些空间用 delete 根本不行,此时就需要删除定制器了。
库里面的话,是给 unique_ptr 和 shared_ptr 配备了删除定制器的。
这里的话,自己模拟实现一下 shared_ptr 的删除定制器(在上面的模拟实现里面自己写了的)。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online