智能指针是C++11引入的自动化内存管理工具,通过RAII(资源获取立即初始化)机制自动释放内存,避免内存泄漏和指针悬空的问题。
template<class T> class SmartPtr { public: //RAII SmartPtr(T* ptr) ; _ptr(ptr) {} ~SmartPtr() { cout << "delete[]" << _ptr << endl; delete[] ptr; } //重载运算符,模拟指针行为,方便资源的访问 T& operator*() { return *_ptr; } T* operator->() { return _ptr; } T& operator[](size_t i) { return _ptr[i]; } private: T* _ptr; }; int main() { SmartPtr<int> sp1 = new int[10]; SmartPtr<pair<int, int>> sp2 = new pair<int, int>[10]; sp1[5] = 10; sp2->first = 1; sp2->second = 2; return 0; }
#include <memory>
class Date
{
public:
Date() = default;
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
//本质是想实现ap1和ap2共同管理一份Date资源
auto_ptr<Date> ap1(new Date);
//拷贝时,管理权转移,被拷贝对象ap1悬空 访问会出错
auto_ptr<Date> ap2(ap1);
return 0;
}
unique_ptr<Date> up1(new Date); //不支持拷贝 //unique_ptr<Date> up2(up1); //支持移动 unique_ptr<Date> up3(move(up1));
引用计数是一种内存管理技术,通过跟踪对象的引用次数来决定何时释放其占用的资源。当引用计数降为0时,对象会被自动销毁。
shared_ptr<Date> sp1(new Date); //拷贝 shared_ptr<Date> sp2(sp1); //移动 shared_ptr<Date> sp3(move(sp1));
智能指针析构时,默认使用delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。示例:
int main() { shared_ptr<Date> sp(new Date[10]); //err return 0; }
上述代码运行会崩溃,对于这种情况,shared_ptr和unique_ptr都特化了一个[ ]版本。
shared_ptr<Date[ ]> sp(new Date[ ]) ; unique_ptr<Date[ ]> up(new Date[ ])。
这样就可以解决new[ ]的问题了。
但是这只是解决了new和new[ ]的问题,还有其他无法删除的情况。为了解决这个问题,智能指针支持在构造时给一个删除器,删除器本质是一个可调用 对象,比如仿函数,lambda表达式,函数指针等等。这个 可调用对象实现你想要释放资源的方式,当构造智能指针时,给了定制的删除器,在智能指针析构时,就会调用删除其释放资源。
库中shared_ptr的构造:

使用:
//仿函数做删除器 template<class T> class DeleteArray { public: void operator()(T* ptr) { delete[ ] ptr; ptr = nullptr; } }; class Fclose { public: void operator()(FILE* ptr) { fclose(ptr); } }; //函数指针做删除器 template<class T> void DeleteArrayFunc(T* ptr) { delete[ ] ptr; } int main() { //函数指针做删除器 shared_ptr<Date> sp1(new Date[10], DeleteArrayFunc<Date>); //lambda表达式做删除器 auto del = [ ](Date* ptr) {delete[] ptr; }; shared_ptr<Date> sp2(new Date[10], del); //仿函数做删除器 shared_ptr<FILE> sp3(fopen("test.cpp","r"), Fclose()); //lambda shared_ptr<FILE> sp4(fopen("test.cpp", "r"), [ ](FILE* ptr) {fclose(ptr); }); return 0; }
shared_ptr要想支持拷贝,也就是几个对象共同管理一份资源,需要用到引用计数。
引用计数是一种内存管理技术,通过跟踪对象的引用次数来决定何时释放其占用的资源。当引用计数降为0时,对象会被自动销毁。
如果我们用count来记录一份资源被多少个对象管理,那么常见的是将count设为静态成员函数,新增一个管理对象count就++。如果这样设计,会造成下面的问题:

sp1和sp2共同管理资源1,引用计数为2,当定义一个新的对象sp3管理资源2时,引用计数会错误的为3。 所以这种方式是不行的。要使用堆上动态开辟的方式,构造智能指针对象时,就new一个引用计数。多个shared_ptr指向同一个资源时,++引用计数,析构时--引用计数,引用计数减为0,代表当前析构的shared_ptr是最后一个管理资源的对象,则析构资源。
以管理Date类为例:
class Date { public: Date() = default; Date(int year, int month, int day) :_year(year) , _month(month) , _day(day) {} ~Date() { cout << "~Date()" << endl; } private: int _year; int _month; int _day; }; template<class T> class shared_ptr { public: //构造函数 shared_ptr(T* ptr=nullptr) :_ptr(ptr) ,_pcount(new int(1)) {} //...... //...... private: T* ptr; int* pcount;//引用计数器 };
shared_ptr拷贝构造的实现:
//拷贝构造 shared_ptr(const shared_ptr& sp) :_ptr(sp._ptr) , _pcount(sp._pcount) { (*_pcount)++; //引用计数++ }
shared_ptr赋值运算符的重载:
//赋值 sp1=sp2 shared_ptr<T>& operator=(shared_ptr<T>& sp) { //1,防止自己给自己赋值, //2,如果sp1和sp2管理同一份资源,也没必要赋值 if (_ptr != sp._ptr) { //sp1之前管理的资源引用计数--,如果等于0,就释放之前的资源 if (--(*_pcount) == 0) { delete _ptr; delete _Pcount; } _pcount = sp.pcount; _ptr = sp._ptr; ++(*_pcount); } return *this; }
定制删除器的实现:
这里和标准库库里的实现保持一值,在构造时传一个删除器。在类中定义删除器的时候,因为我们无法判断它的类型,所以无法定义,我们可以用一个包装器function来包装这个删除器。
function<(void)T*> _del = [](T* ptr) {delete ptr; }; //不传删除器时,默认为delete //含删除器的构造 shared_ptr(T* ptr,D del) :_ptr(ptr) , _pcount(new int(1)) ,_del(del) {}
shared_ptr模拟实现代码:
template<class T>
class shared_ptr
{
public:
//构造函数
shared_ptr(T* ptr=nullptr)
:_ptr(ptr)
,_pcount(new int(1))
{}
//含删除器的构造
shared_ptr(T* ptr,D del)
:_ptr(ptr)
, _pcount(new int(1))
,_del(del)
{}
//拷贝构造
shared_ptr(const shared_ptr& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
,_del(sp._del)
{
(*_pcount)++;
}
void release()
{
if (--(*_pcount) == 0)
{
//delete _ptr;
_del _ptr;
delete _pcount;
_ptr = nullptr;
_pcount = nullptr;
}
}
//赋值 sp1=sp2
shared_ptr<T>& operator=(shared_ptr<T>& sp)
{
//防止自己给自己赋值
if (_ptr != sp._ptr)
{
release();
_pcount = sp.pcount;
_ptr = sp._ptr;
++(*_pcount);
}
return *this;
}
//析构函数
~shared_ptr()
{
release();
}
int use_count()
{
return *_pcount;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
int* _pcount;//引用计数器
function<void(T*)> _del = [](T* ptr) {delete ptr; }; //删除器
};shared_ptr大多数情况管理资源非常合适,支持RAII,也支持拷贝。但在循环引用场景下会导致内存泄漏。如下情节:
我们想实现一个链表结构,由shared_ptr管理。
struct ListNode { int _data; ListNode* next; ListNode* prev; ~ListNode() { cout << "~ListNode()" << endl; } }; int main() { shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); //n1->next = n2; //err return 0; }
上面的写法无法形成链表,因为n2是shared_ptr类型的,而n1->next是ListNode* 类型的,不能赋值。需要改成shared_ptr<ListNode> prev; shared_ptr<ListNode> next;
struct ListNode { int _data; shared_ptr<ListNode> next; shared_ptr<ListNode> prev; ~ListNode() { cout << "~ListNode()" << endl; } }; int main() { shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); n1->next = n2; n2->prev = n1; return 0; }


程序结束,析构时:

运行结果:

没有调用析构,节点资源没有释放。导致内存泄漏的问题。
解决办法,使用weak_ptr:

weak_ptr支持shared_ptr参数的构造,而且weak_ptr是不支持资源管理的,不支持RAII。所以用weak_ptr指向节点时,是不会增加 引用计数的。
struct ListNode { int _data; weak_ptr<ListNode> next; weak_ptr<ListNode> prev; ~ListNode() { cout << "~ListNode()" << endl; } }; int main() { shared_ptr<ListNode> n1(new ListNode); shared_ptr<ListNode> n2(new ListNode); n1->next = n2; n2->prev = n1; return 0; }

weak_ptr不支持资源管理,如果它绑定的shared_ptr已经释放资源了,那么它去访问资源是很危险的。
weak_ptr支持expired检查指向的资源是否过期,use_count也可以获取shared_ptr的引用计数。 weaak_ptr想访问资源时,可以调用lock函数返回一个管理资源的,如果资源已经释放,返回的shared_ptr是一个空对象,如果没有释放,则通过shared_ptr访问资源时安全的。
示例:
int main() { shared_ptr<string> sp1(new string("1111111")); shared_ptr<string> sp2(sp1); weak_ptr<string> wp(sp1); cout << wp.expired() << endl; //false表示资源没有过期 cout << wp.use_count() << endl; return 0; }

std::make_shared 用于创建一个 std::shared_ptr,它比直接使用 std::shared_ptr 的构造函数更高效,因为它会同时分配对象和控制块(用于引用计数等管理信息)的内存,而不是分别分配。
#include <iostream> #include <memory> class MyClass { public: int value; MyClass(int v) : value(v) { std::cout << "Object created with value: " << value << std::endl; } ~MyClass() { std::cout << "Object destroyed" << std::endl; } }; int main() { // 使用 make_shared 创建 shared_ptr auto ptr = std::make_shared<MyClass>(42); // 使用 shared_ptr std::cout << "Value: " << ptr->value << std::endl; return 0; }
shared_ptr还重载了operator bool,可以判断一个shared_ptr对象是否管理着资源。没有管理资源返回false,否则返回true。
#include <iostream> #include <memory> int main() { std::shared_ptr<int> sp1; std::shared_ptr<int> sp2(new int(34)); if (sp1) std::cout << "sp1 points to " << endl; else std::cout << "sp1 is null\n"; if (sp2) std::cout << "sp2 points to " << endl; else std::cout << "sp2 is null\n"; return 0; }
