智能指针

本文详细介绍了C++11中的智能指针,包括shared_ptr、unique_ptr和weak_ptr的使用。智能指针旨在解决内存泄漏问题,通过实例演示了它们的构造、赋值、拷贝以及在数组、传参等方面的用法,并讨论了避免循环引用的方法。文章指出,unique_ptr在多线程环境下更安全,一般情况下推荐使用。

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字样
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值