智能指针的使用场景
正常情况下我们new出来的资源,经过delete以后会正确释放,但是因为抛异常,会导致后面的delete没有执行,所以内存就泄露了,所以我们需要在new以后捕获异常,捕获到异常后delete内存,再把异常抛出来。但是new本身也会抛异常,除法函数也会抛异常**,处理起来很麻烦智能指针解决这样的问题非常好。**
cpp
/////////////////////////////////////////////场景////////////////////////////////////
double Divide(int a, int b)
{
if (b == 0)
throw "Divide by zero condition";
else
{
return (double)a / (double)b;
}
}
void Func()
{
// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的ptr1和ptr2没有得到释放。
// 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。
// 但是如果ptr2new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案
// 是智能指针,否则代码太戳了
int* ptr1 = new int[10];
int* ptr2 = new int[10];
try
{
int len, time;
cin >> len >> time;
Divide(len, time);
}
catch (...)
{
delete[] ptr1;
delete[] ptr2;
cout << "ptr1" << endl;
cout << "ptr2" << endl;
throw;
}
delete[] ptr1;
delete[] ptr2;
}
int main()
{
try
{
Func();
}
catch(const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
}
return 0;
}
RAII和智能指针的设计思路
RAII资源获得立即初始化,它是一种管理类的设计思想,本质是一种利用生命周期来管理获取到的动态资源,避免资源泄露,这里的资源可以说内存,文件指针,网络连接,互斥锁等等。
RAII在获取到一个资源时把资源委托给一个对象,控制对资源的访问,资源在对象生命周期内始终保持有效,最后对象在析构的时候释放资源,保障了资源的正常释放,避免了资源泄露问题。
智能指针除了要满足RAII思路,还要方便访问资源,所以还会像迭代器重载一些操作符operator*/operator->/operator[],方便访问资源。
cpp
template<class T>
class SmartPtr
{
public:
SmartPtr(T* ptr)
:_ptr(ptr)
{ }
~SmartPtr()
{
cout << "delete [] _ptr" << endl;
delete[] _ptr;
}
//返回一个值
T& operator*()
{
return *_ptr;
}
T& operator[](size_t i)
{
return _ptr[i];
}
//返回一个指针
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
double Divide(int a, int b)
{
if (b == 0)
throw "Divide by zero condition";
else
{
return (double)a / (double)b;
}
}
void Func()
{
// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的ptr1和ptr2没有得到释放。
// 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。
// 但是如果ptr2new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案
// 是智能指针,否则代码太戳了
SmartPtr<int> ptr1 = new int[10];
SmartPtr<int> ptr2 = new int[10];
int len, time;
cin >> len >> time;
Divide(len, time);
ptr1[0] = 10;
cout << *(ptr1) << endl;
}
int main()
{
try
{
Func();
}
catch(const char* errmsg)
{
cout << errmsg << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
catch (...)
{
}
return 0;
}
C++标准库智能指针的使用
头文件<memory>
有多种智能指针,原理上主要是智能指针解决拷贝时的思路不同。
C++98,auto_ptr,特点是拷贝时会把被拷贝对象的资源管理权转让给拷贝对象。它会导致被拷贝对象悬空,访问以后会导致报错。
C++11,unique_ptr,唯一指针,特点不支持拷贝,只支持移动。
C++11,shared_ptr,共享指针,特点可以拷贝,也可以移动。底层是引用计数实现的。
C++11,weak_ptr,弱指针,它不会直接管理资源,解决shared_ptr的循环引用导致的内存泄露的问题。
智能指针析构时,默认用delete来释放资源。如果资源不是 new出来的,交给智能指针来管理,析构就会奔溃。
智能指针在构造时给一个删除器,删除器本质是一个可调用对象,这个可调用对象中实现你释放资源的方式,当智能指针构造时,给了定制的删除器,在智能指针析构的时候,就会调用删除器去释放资源。
cpp
/////////////////////////////////////////////////////C++标准库的指针/////////////////////////////////////////////////////////
struct Date
{
int _year;
int _month;
int _day;
//Date() = default;
Date(int year=1, int month=1, int day=1)
:_year(year)
, _month(month)
, _day(day)
{
}
~Date()
{
cout << "~Date()" << endl;
}
};
int main()
{
//构造函数只能如此
auto_ptr<Date> ap1(new Date);
//管理权转让给ap2 ap1悬空
auto_ptr<Date> ap2(ap1);
//编译时不报错 程序会奔溃
//ap1->_year++;
unique_ptr<Date> up1(new Date);
//不支持拷贝
//unique_ptr<Date> up2(up1);
//支持移动 移动后的up1也会悬空
unique_ptr<Date> up3(move(up1));
shared_ptr<Date> sp1(new Date);
//支持拷贝 支持移动
shared_ptr<Date> sp2(sp1);
//move 以后也会悬空
shared_ptr<Date> sp3(sp2);
cout << sp1.use_count() << endl;
sp1->_year++;
cout << sp1->_year << endl;
cout << sp2->_year << endl;
cout << sp3->_year << endl;
return 0;
}
cpp
class Fclose
{
public:
void operator()(FILE* ptr)
{
cout << "Fclose" << endl;
fclose(ptr);
}
};
//lambda表达式
//函数指针
template<class T>
void DeleteArrayFunc(T* ptr)
{
delete[] ptr;
}
//仿函数对象
template<class T>
class DeleteArray
{
public:
void operator()(T* ptr)
{
delete[] ptr;
}
};
//int main()
//{
// ////////////////////////////////////////////////shared_ptr//////////////////////////////////////////////
// // 定义删除器建议lambda表达式
// //std::shared_ptr<Date>sp1(new Date);
// ////默认是用delete来释放资源
// //std::shared_ptr<Date[]>sp2(new Date[10]);
//
// //std::shared_ptr<Date>sp5(new Date[10], DeleteArrayFunc<Date>); //模板的函数指针
// //std::shared_ptr<Date>sp6(new Date[10], [](Date* ptr) {delete[] ptr; }); //lambda表达式
// //std::shared_ptr<Date>sp6(new Date[10], DeleteArray<Date>()); //仿函数对象
//
//
// //std::shared_ptr<FILE>sp3(fopen("Test.cpp", "r"), Fclose());
// //std::shared_ptr<FILE>sp4(fopen("Test.cpp", "r"), [](FILE* pf) {fclose(pf); });
//
//
//
// ////////////////////////////////////////////////unique_ptr//////////////////////////////////////////////
// //定制删除器建议仿函数
// //std::unique_ptr<Date>up1(new Date);
// //std::unique_ptr<Date[]>up2(new Date[10]);
//
//
//
//
//
// //auto deleteArray = [](Date* ptr) {delete[] ptr; };
// //std::unique_ptr<Date, decltype(deleteArray)>up4(new Date[10],deleteArray); //lambda表达式
//
//
// //std::unique_ptr<Date, DeleteArray<Date>>up5(new Date[10]); //仿函数对象
//
//
//
// //std::unique_ptr<Date>up5(new Date[10]);
//
// //std::unique_ptr<FILE, Fclose>up6(fopen("test.cpp", "r"), Fclose()); //仿函数对象
// //std::unique_ptr<FILE, Fclose>up7(fopen("test.cpp", "r"));
//
//
// //auto pf = [](FILE* pf) {cout<<"fclose" << endl; fclose(pf); }; //lambda表达式
// //std::unique_ptr<FILE, decltype(pf) >up8(fopen("Test.cpp", "r"),pf);
// return 0;
//}
cpp
#include<functional>
namespace jiege
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{ }
~auto_ptr()
{
if (_ptr)
{
delete _ptr;
}
}
//p1(p2)
auto_ptr(const auto_ptr<T>& sp)
:_ptr(sp._ptr)
{
sp._ptr = nullptr;
}
//p1=p2
auto_ptr<T>& operator=(auto_ptr<T>& sp)
{
if (this != sp)
{
_ptr = sp;
sp._ptr = nullptr;
}
return *this;
}
//像指针一样的使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{
}
~unique_ptr()
{
if (_ptr)
{
delete[] _ptr;
}
}
//删除拷贝和拷贝赋值
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
unique_ptr(unique_ptr<T>&& sp)
:_ptr(sp._ptr)
{
_ptr = nullptr;
}
//p1=p2
unique_ptr<T>& operator=(unique_ptr<T>&& sp)
{
if (this != sp)
{
//释放原来的资源
delete _ptr;
_ptr = sp;
sp._ptr = nullptr;
}
return *this;
}
//像指针一样的使用
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
template<class T>
class shared_ptr
{
public:
explicit shared_ptr(T* ptr)
:_ptr(ptr)
,_pcount(new atomic<int>int(1))
{ }
template<class D>
explicit shared_ptr(T* ptr,D del)
: _ptr(ptr)
, _pcount(new atomic<int>int(1))
,_del(del)
{
}
~shared_ptr()
{
if (--(*_pcount)==0)
{
_del(_ptr);
delete _pcount;
}
}
//p2(p1)共同管理资源
shared_ptr(const shared_ptr<T>& sp)//
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
++(*_pcount);
}
//sp1=sp4 //sp2原来管理一份资源
shared_ptr<T>& operator=(const shared_ptr<T>& sp)
{
if (_ptr != sp._ptr)
{
if (--(*_pcount) == 0)
{
delete _ptr;
delete _pcount;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
}
return *this;
}
//sp2 (sp1)
shared_ptr(shared_ptr<T>&& sp)
:_ptr(sp._ptr)
, _pcount(sp._pcount)
{
delete sp._ptr;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int use_count()
{
return *_pcount;
}
private:
T* _ptr;
//int* _pcount;
atomic<int>* _pcount;
function<void(T*)>_del = [](T* ptr) {delete ptr; };
};
}
struct Date
{
int _year;
int _month;
int _day;
//Date() = default;
Date(int year=1, int month=1, int day=1)
:_year(year)
, _month(month)
, _day(day)
{
}
~Date()
{
cout << "~Date()" << endl;
}
};
//int main()
//{
// jiege::shared_ptr<Date>sp1(new Date);
//
// jiege::shared_ptr<Date>sp2(new Date[10], [](Date* ptr) {delete[] ptr; });
//
// return 0;
//}
//int main()
//{
// jiege::shared_ptr<Date> sp1(new Date);
// jiege::shared_ptr<Date> sp2(sp1);
// jiege::shared_ptr<Date> sp3 = sp2;
//
// jiege::shared_ptr<Date> sp4(new Date);
//
// sp1 = sp4;
// //sp1 = sp2;
// return 0;
//}
cpp
int main()
{
std::shared_ptr<Date>sp1(new Date);
std::shared_ptr<Date>sp2 = make_shared<Date>(2024, 3, 27);//用初始化资源对象的值直接构造
auto sp3 = make_shared<Date>(2025, 12, 19);
//if(sp1.operator bool()如果智能指针对象是⼀个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。
if (sp1)
cout << "sp1 is not nullptr" << endl;
std::shared_ptr<Date> sp4;
if (!sp4)
cout << "sp2 is nullptr" << endl;
//普通指针不能向智能指针转换 explicit
//std::shared_ptr < Date > sp5= new Date(2025, 12, 19);
return 0;
}
shared_ptr和weak_ptr循环引用和解决方式
循环引用场景

如下图所述场景,n1和n2析构后,管理两个节点的引⽤计数减到1
- 右边的节点什么时候释放呢,左边节点中的_next管着呢,_next析构后,右边的节点就释放了。
- _next什么时候析构呢,_next是左边节点的的成员,左边节点释放,_next就析构了。
- 左边节点什么时候释放呢,左边节点由右边节点中的_prev管着呢,_prev析构后,左边的节点就释
放了。 - _prev什么时候析构呢,_prev是右边节点的成员,右边节点释放,_prev就析构了。
•
⾄此逻辑上成功形成回旋镖似的循环引⽤,谁都不会释放就形成了循环引⽤,导致内存泄漏
•
把ListNode结构体中的_next和_prev改成weak_ptr,weak_ptr绑定到shared_ptr时不会增加它的
引⽤计数,_next和_prev不参与资源释放管理逻辑,就成功打破了循环引⽤,解决了这⾥的问题
cpp
struct ListNode
{
int _data;
//ListNode* _next;
//ListNode* _prev;//n1->next=n2; n2是智能指针 n1->next是原生指针 互相不存在抓换关系
//std::shared_ptr<ListNode> _next;
//std::shared_ptr<ListNode> _prev;
//当n1->next=n2;weak_ptr 不增加n2引用计数 不参数资源的管理但会指向资源 就不会形成循环引用
//
std::weak_ptr<ListNode> _next;
std::weak_ptr<ListNode> _prev;
~ListNode()
{
cout << "~ListNode()" << endl;
}
};
//int main()
//{
// std::shared_ptr<ListNode> n1(new ListNode);
// std::shared_ptr<ListNode> n2(new ListNode);
//
// n1->_next = n2;
// n2->_prev = n1;
//
// cout << n1.use_count() << endl;
// cout << n2.use_count() << endl;
//
// return 0;
//}
weak_ptr
weak_ptr不支持RAII,也不支持访问资源,weak_ptr构造,不支持绑定资源**,只支持绑定到shared_ptr,但是不会增加引用计数**,就解决了上述问题。
没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的
shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的
资源是否过期,use_count也可获取shared_ptr的引⽤计数,weak_ptr想访问资源时,可以调⽤
lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如
果资源没有释放,则通过返回的shared_ptr访问资源是安全的
cpp
int main()
{
std::shared_ptr<string>sp1(new string("11111111111111111"));
std::shared_ptr<string>sp2(sp1);
std::weak_ptr<string>wp = sp1;
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
sp1 = make_shared<string>("22222222222222");
auto sp3 = wp.lock();
cout << wp.expired() << endl;
cout << wp.use_count() << endl;
return 0;
}
shared_ptr线程安全问题
shared_ptr的引⽤计数对象在堆上,如果多个shared_ptr对象在多个线程中,进⾏shared_ptr的拷
⻉析构时会访问修改引⽤计数,就会存在线程安全问题,所以shared_ptr引⽤计数是需要加锁或者
原⼦操作保证线程安全的。
•
shared_ptr指向的对象也是有线程安全的问题的,但是这个对象的线程安全问题不归shared_ptr
管,它也管不了,应该有外层使⽤shared_ptr的⼈进⾏线程安全的控制。
•
下⾯的程序会崩溃或者A资源没释放,bit::shared_ptr引⽤计数从int*改成atomic<int>*就可以保证
引⽤计数的线程安全问题,或者使⽤互斥锁加锁也可以。
cpp
#include<thread>
struct AA
{
int _a1 = 0;
int _a2 = 0;
~AA()
{
cout << "~AA()" << endl;
}
};
int main()
{
jiege::shared_ptr<AA> p(new AA);
const size_t n = 100000;
mutex mtx;
auto func = [&]()
{
for (size_t i = 0; i < n; ++i)
{
// 这⾥智能指针拷⻉会++计数
jiege::shared_ptr<AA> copy(p);
{
unique_lock<mutex> lk(mtx);
copy->_a1++;
copy->_a2++;
}
}
};
thread t1(func);
thread t2(func);
t1.join();
t2.join();
cout << p->_a1 << endl;
cout << p->_a2 << endl;
cout << p.use_count() << endl;
return 0;
}
Boost社区
Boost社区的发起⼈Dawes本⼈就是C++标准委员会的成员
之⼀。在Boost库的开发中,Boost社区也在这个⽅向上取得了丰硕的成果,C++11及之后的新语法
和库有很多都是从Boost中来的。
内存泄露
什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存,⼀般是忘记释放或者发⽣异常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消失,⽽是应⽤程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费。
内存泄漏的危害:普通程序运⾏⼀会就结束了出现内存泄漏问题也不⼤,进程正常结束,⻚表的映射关系解除,物理内存也可以释放。
⻓期运⾏的程序出现内存泄漏,影响很⼤,如操作系统、后台服务,⻓时间运⾏的客⼾端等等,不断出现内存泄漏会导致可⽤内存不断变少,各种功能响应越来越慢,最终卡死。
如何避免内存泄漏
⼯程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。
尽量使⽤智能指针来管理资源,如果⾃⼰场景⽐较特殊,采⽤RAII思想⾃⼰造个轮⼦管理。
检测工具