C++11引用了智能指针来解决内存泄漏的问题,特别在大型项目中new和delete频繁使用后,导致后期维护成本的持续走高。智能指针可以很好解决这一问题,但智能指针的使用也存在一些陷阱,只有熟练使用之后才能让它成为利器。为了更好地反应问题现象,先封装一个检测内存泄漏的API来进行测试,下面就shared_ptr,unique_ptr,weak_ptr进行说明。
- 内存检测api
#ifndef _MEMORYLEAKCHECK_H__
#define _MEMORYLEAKCHECK_H__
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#include <assert.h>
#ifdef _DEBUG
# define new DEBUG_CLIENTBLOCK
#endif
#ifdef _DEBUG
# define DEBUG_CLIENTBLOCK new( _CLIENT_BLOCK, __FILE__, __LINE__)
#else
# define DEBUG_CLIENTBLOCK
#endif
void ExitCheckout()
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
assert(_CrtDumpMemoryLeaks() == 0);
}
#endif /*_MEMORYLEAKCHECK_H__*/
- 测试类
smart.h
#ifndef _SMART_H__
#define _SMART_H__
#include <memory>
typedef struct
{
int iFrameHeader;
short sDataLen;
char acData[1];
int iFrameTail;
}ST_DATA, *PST_DATA;
class CSmart;
typedef std::weak_ptr<CSmart> CSmartW;
typedef std::unique_ptr<CSmart> CSmartU;
typedef std::shared_ptr<CSmart> CSmartS;
class CSmart : public std::enable_shared_from_this<CSmart>
{
public:
int m_iTest;
public:
void Print();
void Set(int iData);
CSmartS GetObject();
std::shared_ptr<char> GetSharedFrame(int iDataLen);
std::unique_ptr<char[]> GetUniqueFrame(int iDataLen);
void SetsharedFrame(std::shared_ptr<char> pcData);
void SetUniqueFrame(std::unique_ptr<char[]>& pcData);
public:
CSmart();
~CSmart();
//CSmart(const CSmart&) = delete;
//CSmart& operator = (const CSmart&) = delete;
//~CSmart() = default;
};
//============================================================
class CBaseB;
class CBaseA
{
public:
std::weak_ptr<CBaseB> m_pobjBaseB;
//std::shared_ptr<CBaseB> m_pobjBaseB;
public:
void Print() { printf("CBaseA\n"); }
public:
CBaseA() = default;
~CBaseA() { printf("~CBaseA\n"); }
};
class CBaseB
{
public:
std::weak_ptr<CBaseA> m_pobjBaseA;
//std::shared_ptr<CBaseA> m_pobjBaseA;
public:
void Print() { printf("CBaseB\n"); }
public:
CBaseB() = default;
~CBaseB() = default;
};
#endif /*_SMART_H__*/
smart.cpp
#include "stdafx.h"
#include "smart.h"
CSmart::CSmart():m_iTest(0)
{
}
CSmart::~CSmart()
{
printf("~CSmart\n");
}
void CSmart::Print()
{
printf("function:test\n");
}
void CSmart::Set(int iData)
{
m_iTest = iData;
printf("set value:%d\n", m_iTest);
}
CSmartS CSmart::GetObject()
{
//return (CSmartS)this;
return shared_from_this();
}
std::shared_ptr<char> CSmart::GetSharedFrame(int iDataLen)
{
std::shared_ptr<char> pstChar(new char[sizeof (ST_DATA)+iDataLen - 1](), std::default_delete<char[]>());
PST_DATA pstData = (PST_DATA)(pstChar.get());
pstData->iFrameHeader = 0xFF;
pstData->sDataLen = iDataLen;
pstData->iFrameTail = 0xAA;
return pstChar;
}
std::unique_ptr<char[]> CSmart::GetUniqueFrame(int iDataLen)
{
std::unique_ptr<char[]> pstChar(new char[sizeof (ST_DATA) + iDataLen - 1]());
PST_DATA pstData = (PST_DATA)(pstChar.get());
pstData->iFrameHeader = 0xFF;
pstData->sDataLen = iDataLen;
pstData->iFrameTail = 0xAA;
return pstChar;
}
void CSmart::SetsharedFrame(std::shared_ptr<char> pcData)
{
if (nullptr != pcData.get())
{
PST_DATA pstData = (PST_DATA)(pcData.get());
pstData->iFrameHeader = 0xFF;
pstData->sDataLen = 10;
pstData->iFrameTail = 0xAA;
}
}
void CSmart::SetUniqueFrame(std::unique_ptr<char[]>& pcData)
{
if (nullptr != pcData.get())
{
PST_DATA pstData = (PST_DATA)(pcData.get());
pstData->iFrameHeader = 0xFF;
pstData->sDataLen = 10;
pstData->iFrameTail = 0xAA;
}
}
1.shared_ptr
- 构造方式:使用make_shared开销更小,内置use_count计数,计数为0,则释放内存
//方式一
std::shared_ptr<CSmart> pobjSmartA(new CSmart());
pobjSmartA->Print();
//方式二
std::shared_ptr<CSmart> pobjSmart = std::make_shared<CSmart>();/*make_shared():下面赋值和拷贝不会再new*/
pobjSmart->Print();
- 赋值和拷贝:shared_ptr指向的内存被共享,赋值和拷贝后便可以对其内存进行操作
std::shared_ptr<CSmart> pobjSmart1(pobjSmart);//赋值,count+1
pobjSmart1->Print();
std::shared_ptr<CSmart> pobjSmart2 = pobjSmart;//拷贝,count+1
pobjSmart2->Print();
printf("count:%d\n", pobjSmart2.use_count());//count = 3
- this传递给shared_ptr :不能直接回传this指针
(1)直接传递测试:存在内存泄漏
/*smart.cpp*/
CSmartS CSmart::GetObject()
{
return (CSmartS)this;
//return shared_from_this();
}
... ...
/*main.cpp*/
CSmartS pobjGet = pobjSmart->GetObject();//this指针给shared_ptr,会导致内存泄漏
pobjGet->Print();
结果:

(2)正确传递方式:不存在内存泄漏
/*smart.h*/
class CSmart : public std::enable_shared_from_this<CSmart>
{
... ...
}
/*smart.cpp*/
CSmartS CSmart::GetObject()
{
//return (CSmartS)this;
return shared_from_this();
}
... ...
/*main.cpp*/
CSmartS pobjGet = pobjSmart->GetObject();//引用shared_from_this不会造成内存泄漏
pobjGet->Print();
- shared_ptr作为对象的成员时,相互引用会造成循环引用无法释放
(1)还原场景:shared_ptr作为两个对象指针相互引用,count计数为2,离开作用域时count计数为1,不释放
class CBaseB;
class CBaseA
{
public:
//std::weak_ptr<CBaseB> m_pobjBaseB;
std::shared_ptr<CBaseB> m_pobjBaseB;
public:
void Print() { printf("CBaseA\n"); }
public:
CBaseA() = default;
~CBaseA() { printf("~CBaseA\n"); }
};
class CBaseB
{
public:
//std::weak_ptr<CBaseA> m_pobjBaseA;
std::shared_ptr<CBaseA> m_pobjBaseA;
public:
void Print() { printf("CBaseB\n"); }
public:
CBaseB() = default;
~CBaseB() = default;
};
... ...
/*main.cpp*/
std::shared_ptr<CBaseA> pobjBaseA = std::make_shared<CBaseA>();
std::shared_ptr<CBaseB> pobjBaseB = std::make_shared<CBaseB>();
pobjBaseA->m_pobjBaseB = pobjBaseB;
pobjBaseB->m_pobjBaseA = pobjBaseA;
printf("CBaseA count:%d CBaseB count:%d\n", pobjBaseA.use_count(), pobjBaseB.use_count());
结果 :

(2)解决方式:使用weak_ptr,弱引用,count不计数,count计数为1,离开作用域后为0,释放内存块,不存在内存泄漏
class CBaseB;
class CBaseA
{
public:
std::weak_ptr<CBaseB> m_pobjBaseB;
//std::shared_ptr<CBaseB> m_pobjBaseB;
public:
void Print() { printf("CBaseA\n"); }
public:
CBaseA() = default;
~CBaseA() { printf("~CBaseA\n"); }
};
class CBaseB
{
public:
std::weak_ptr<CBaseA> m_pobjBaseA;
//std::shared_ptr<CBaseA> m_pobjBaseA;
public:
void Print() { printf("CBaseB\n"); }
public:
CBaseB() = default;
~CBaseB() = default;
};
... ...
/*main.cpp*/
std::shared_ptr<CBaseA> pobjBaseA = std::make_shared<CBaseA>();
std::shared_ptr<CBaseB> pobjBaseB = std::make_shared<CBaseB>();
pobjBaseA->m_pobjBaseB = pobjBaseB;
pobjBaseB->m_pobjBaseA = pobjBaseA;
/*weak_ptr不能直接使用,lock()会检测对象是否存在,返回一个shared_ptr类型*/
(pobjBaseA->m_pobjBaseB.lock())->Print();
(pobjBaseB->m_pobjBaseA.lock())->Print();
printf("CBaseA count:%d CBaseB count:%d\n", pobjBaseA.use_count(), pobjBaseB.use_count());
- 构造数组:shared_ptr构造数组需要指定删除器
(1)构造方式1:使用C++11默认的删除器:std::default_delete<int[]>()
std::shared_ptr<int> piData1(new int[10], std::default_delete<int[]>());//指定删除器:使用C++11里默认的删除器
(piData1.get())[9] = 2;
printf("shared_ptr array element uninit:\n");
for (auto auCount = 0; auCount < 10; auCount++)
{
printf("%d \n", (piData1.get())[auCount]);
}
(2)构造方式2:使用lambda表达式:[](int *piData1){ delete[] piData1; }
std::shared_ptr<int> piData2(new int[10](), [](int *piData1){ delete[] piData1; });//指定删除器:使用lambda表达式
(piData2.get())[0] = 4;
printf("shared_ptr array element init:\n");
for (auto auCount = 0; auCount < 10; auCount++)
{
printf("%d \n", (piData2.get())[auCount]);
}
(3)初始化:std::shared_ptr<int> piData2(new int[10](), [](int *piData1){ delete[] piData1; });加个括号就行了
(4)操作数组元素:int* pData = piData2.get()获取原生指针
(5)对比unique_ptr而言,unique_ptr更适合用来构造数组,后面unique_ptr会进行说明
- shared_ptr作为传入,传出参数:主要测试其通用性(对比普通函数)
(1)作为传出参数:不同于拷贝,count计数不会加1
/*smart.h*/
typedef struct
{
int iFrameHeader;
short sDataLen;
char acData[1];
int iFrameTail;
}ST_DATA, *PST_DATA;
... ...
/*smart.cpp*/
std::shared_ptr<char> CSmart::GetSharedFrame(int iDataLen)
{
std::shared_ptr<char> pstChar(new char[sizeof (ST_DATA)+iDataLen - 1](), std::default_delete<char[]>());
PST_DATA pstData = (PST_DATA)(pstChar.get());
pstData->iFrameHeader = 0xFF;
pstData->sDataLen = iDataLen;
pstData->iFrameTail = 0xAA;
return pstChar;
}
... ...
/*main.cpp*/
auto pauData1 = pobjSmart->GetSharedFrame(5);
printf("count:%d\n", pauData1.use_count());
if (nullptr != pauData1.get())
{
PST_DATA pstData = (PST_DATA)(pauData1.get());
printf("header:%d datelen:%d tail:%d\n", pstData->iFrameHeader, pstData->sDataLen, pstData->iFrameTail);
}
(2)作为传入参数
/*smart.h*/
typedef struct
{
int iFrameHeader;
short sDataLen;
char acData[1];
int iFrameTail;
}ST_DATA, *PST_DATA;
... ...
/*smart.cpp*/
void CSmart::SetsharedFrame(std::shared_ptr<char> pcData)
{
if (nullptr != pcData.get())
{
PST_DATA pstData = (PST_DATA)(pcData.get());
pstData->iFrameHeader = 0xFF;
pstData->sDataLen = 10;
pstData->iFrameTail = 0xAA;
}
}
... ...
/*main.cpp*/
std::shared_ptr<char> pacData1(new char[sizeof (ST_DATA)](), std::default_delete<char[]>());
pobjSmart->SetsharedFrame(pacData1);
PST_DATA pstData1 = (PST_DATA)(pacData1.get());
printf("header:%d datelen:%d tail:%d\n", pstData1->iFrameHeader, pstData1->sDataLen, pstData1->iFrameTail);
2.unique_ptr
- 构造方式:与shared_ptr 一样,make函式不同:make_unique<>()这是C++14添加的
std::unique_ptr<CSmart> pobjSmart4 = std::make_unique<CSmart>();
pobjSmart4->Set(2);
- 独占:不允许拷贝和赋值,下面情况编译器会报错
//std::unique_ptr<CSmart> pobjSmart5(pobjSmart4);//赋值语法错误
//std::unique_ptr<CSmart> pobjSmart6 = pobjSmart4;//拷贝语法错误
- move:转换所有权,C++11提出:move之后pojbSmart4不允许再使用,pobjSmart4状态不可控
std::unique_ptr<CSmart> pobjSmart5 = std::move(pobjSmart4);
pobjSmart5->Set(4);
printf("iTest:%d\n", pobjSmart5->m_iTest);
//pobjSmart4->Set(20);/*F5调试报错*/
//printf("iTest:%d\n", pobjSmart4->m_iTest);/*F5调试报错*/
/*move更像是一个右值引用,C++11提出:move之后pojbSmart4不允许再使用,pobjSmart4状态不可控*/
if (nullptr == pobjSmart4.get())/*pobjSmart4获取不到原生指针*/
{
printf("move pobjSmart4\n");
}
需要说明一点:如果不涉及到对象成员的访问,pobjSmart4可以继续调用对象函数(作为普通函数)
eg.void CSmart::Set(int iData) { printf("iData:%d\n", iData); }
pobjSmart4->Set(4);/*不会报错*/
- reset 和 release:不建议原生指针去接收relase,因为涉及到原生指针delete
(1)reset:unique_ptr所指向的内存被释放
std::unique_ptr<CSmart> pobjSmart6 = std::make_unique<CSmart>();
pobjSmart6.reset();/*指向的内存释放,调用CSmart的析构*/
if ((nullptr == pobjSmart6) && (nullptr == pobjSmart6.get()))
{
pobjSmart6->Print();/*函数里不访问对象成员,是可以继续被调用的*/
pobjSmart6->Set(4);/*编译器直接拒绝访问*/
printf("smart6 is nullptr\n");
}
(2)release:只是将指针赋空,unique_ptr所指向的内存并没有被释放,返回原生指针
std::unique_ptr<CSmart> pobjSmart7 = std::make_unique<CSmart>();
CSmart* pSmart = pobjSmart7.release();/*只是将指针赋为空值,原来指向的内存并没有被释放*/
if ((nullptr == pobjSmart7) && (nullptr == pobjSmart7.get()))
{
printf("smart7 is nullptr\n");
}
delete pSmart; /*原生指针是不会被回收的,需要手动释放*/
(3)联合使用:reset释放原来的内存,获取新的内存和管理权
std::unique_ptr<CBaseA> pobjSmart8 = std::make_unique<CBaseA>();
std::unique_ptr<CBaseA> pobjSmart9 = std::make_unique<CBaseA>();
/*调用了析构,说明smart8所指向的对象已经被释放,又重新获取了smart9的资源所有权*/
pobjSmart8.reset(pobjSmart9.release());
if ((nullptr == pobjSmart9) && (nullptr == pobjSmart9.get()) &&
(nullptr != pobjSmart8) && (nullptr != pobjSmart8.get()))
{
printf("smart8 get memeory\n");
}
- 传入/传出参数:通用性测试,对比shared_ptr更安全,特别是在多线程中
(1)传出参数:明明不能拷贝,为什么可以作为传出参数,std::move()的功劳
/*smart.h*/
typedef struct
{
int iFrameHeader;
short sDataLen;
char acData[1];
int iFrameTail;
}ST_DATA, *PST_DATA;
... ...
/*smart.cpp*/
std::unique_ptr<char[]> CSmart::GetUniqueFrame(int iDataLen)
{
std::unique_ptr<char[]> pstChar(new char[sizeof (ST_DATA) + iDataLen - 1]());
PST_DATA pstData = (PST_DATA)(pstChar.get());
pstData->iFrameHeader = 0xFF;
pstData->sDataLen = iDataLen;
pstData->iFrameTail = 0xAA;
return pstChar;
}
... ...
/*main.cpp*/
std::unique_ptr<CSmart> pobjSmart10 = std::make_unique<CSmart>();
/*unique*/
auto pauData = pobjSmart10->GetUniqueFrame(3);/*传出时必须使用智能指针来接收*/
if (nullptr != pauData.get())
{
PST_DATA pstData = (PST_DATA)(pauData.get());
printf("header:%d datelen:%d tail:%d\n", pstData->iFrameHeader, pstData->sDataLen, pstData->iFrameTail);
}
(2)传入参数:不能当作临时变量,要使用"&"传入unique_ptr本身,而通过get()原生指针作为传入参数更适合
/*smart.h*/
typedef struct
{
int iFrameHeader;
short sDataLen;
char acData[1];
int iFrameTail;
}ST_DATA, *PST_DATA;
... ...
/*smart.cpp*/
void CSmart::SetUniqueFrame(std::unique_ptr<char[]>& pcData)
{
if (nullptr != pcData.get())
{
PST_DATA pstData = (PST_DATA)(pcData.get());
pstData->iFrameHeader = 0xFF;
pstData->sDataLen = 10;
pstData->iFrameTail = 0xAA;
}
}
void CSmart::SetUniqueFrame(PST_DATA pstData)
{
if (nullptr != pstData)
{
pstData->iFrameHeader = 0xFF;
pstData->sDataLen = 10;
pstData->iFrameTail = 0xAA;
}
}
... ...
/*main.cpp*/
std::unique_ptr<char[]> pacData(new char[sizeof (ST_DATA)]());
pobjSmart10->SetUniqueFrame(pacData);/*使用引用操作unique_ptr本身*/
PST_DATA pstData = (PST_DATA)(pacData.get());
printf("header:%d datelen:%d tail:%d\n", pstData->iFrameHeader, pstData->sDataLen, pstData->iFrameTail);
pobjSmart10->SetUniqueFrame((PST_DATA)(pacData.get()));/*使用原生指针作为传入参数*/
printf("header:%d datelen:%d tail:%d\n", pstData->iFrameHeader, pstData->sDataLen, pstData->iFrameTail);
- 数组:对比shared_ptr更适合用来生成数组,不需要指定删除器,重载了operate=操作,可以直接piData[1] = 4来赋值
std::unique_ptr<int[]> piData3(new int[10]{0, 1, 2, 4, 5, 6, 7});/*初始化具体值*/
piData3[1] = 4;
printf("unique_ptr array element init:\n");
for (auto auCount = 0; auCount < 10; auCount++)
{
printf("%d \n", piData3[auCount]);
}
- 配合STL使用:为什么要用move,是将所有权转交给STL管理
typedef std::vector<std::unique_ptr<ST_DATA>> VECDATA;
VECDATA vecData;
for (auto auCount = 0; auCount < 4; auCount++)
{
std::unique_ptr<ST_DATA> pstData = std::make_unique<ST_DATA>();
vecData.push_back(std::move(pstData));
}
vecData[1]->iFrameHeader = 1;
vecData[2]->iFrameHeader = 4;
printf("header0:%d header1:%d header2:%d\n", vecData[0]->iFrameHeader, vecData[1]->iFrameHeader, vecData[2]->iFrameHeader);
3.weak_ptr
- 主要配合shared_ptr使用,避免循环使用的悲剧
- 获取shared_ptr对象:lock检测shared_ptr对象是否存在,如果存在,则返回shared_ptr对象
(pobjBaseA->m_pobjBaseB.lock())->Print();
(pobjBaseB->m_pobjBaseA.lock())->Print();
4.总结
- 上述这么多的说明只是为验证智能指针的通用性,事实上也完成了普通指针的功能
- 如果不是特殊情况,更建议使用unique_ptr,操作更安全,特别是在多线程中使用时,shared_ptr共享时可能存在数据需求错乱的情况
- shared_ptr和unique_ptr获取原生指针get(),weak_ptr获取shared_ptr指针lock
- 除了循环引用的问题,其他方面看上去还是很安全的,如果使用智能指针,请禁止使用delete和free字样
本文详细介绍了C++11中的智能指针,包括shared_ptr、unique_ptr和weak_ptr的使用。智能指针旨在解决内存泄漏问题,通过实例演示了它们的构造、赋值、拷贝以及在数组、传参等方面的用法,并讨论了避免循环引用的方法。文章指出,unique_ptr在多线程环境下更安全,一般情况下推荐使用。
2516

被折叠的 条评论
为什么被折叠?



