前言
智能指针的出现大大减轻了 C++
程序员的心理负担(最少对于我是这样的),不用再时时刻刻担心一个 new
出来的指针是否被 delete
的问题了。虽然智能指针很强大,但是如果用不好,还是会导致各种各样的问题。最近,在项目里看到了几种智能指针的典型错误用法。有的严重,有的轻,有的问题在研发阶段并没有立刻暴露出来,埋下了一颗定时炸弹。趁着这个机会,总结一下几种常见的错误用法。希望对各位小伙伴儿有帮助。
说明:智能指针有很多种,标准库提供了几种,比如最常用的 shared_ptr
和 unique_ptr
, 比较少用的 weak_ptr
以及被废弃的 auto_ptr
。项目代码里还有一种基于引用计数的智能指针。本文只列举几种常见的错误用法,不会深入到各种智能指针的实现。如果想深刻理解智能指针的用法,一定要看源码。
错误用法
在列举各种错误用法之前,想说明一点:这些错误都是实际遇到过的,并不是凭空想出来的。所以,即使有些示例代码错的是那么明显,也请不要轻视。
1. 同一指针交给两个智能指针管理导致二次释放
namespace case1
{
/*
* pRawData is already managed by a shared_ptr (pData in function #Entry()),
* in function #Use(), another shared_ptr (pNewData) manages the raw pointer too.
* now, we have two shared_ptr manage the same raw pointer. Oops, double free!
*/
static void Use(Common::CDerived* pRawData)
{
std::shared_ptr<Common::CDerived> pNewData(pRawData);
pNewData->DoSomething();
}
static void Entry()
{
auto pData = std::make_shared<Common::CDerived>();
Use(pData.get());
}
}
2. 错误的动态转换导致的二次释放
#pragma once
#include <memory>
#include "common.h"
namespace case2
{
static void Use(std::shared_ptr<Common::CDerived> pData)
{
pData->DoSomething();
}
static std::shared_ptr<Common::CBase> GetData()
{
return std::make_shared<Common::CDerived>();
}
static void Entry()
{
auto pTest = std::make_shared<Common::CDerived>();
// Oops, double free
std::shared_ptr<Common::CBase> pData = GetData();
auto pRawData = pData.get();
Common::CDerived* pDerived = dynamic_cast<Common::CDerived*>(pRawData);
if (pDerived)
{
Use(std::shared_ptr<Common::CDerived>(pDerived));
}
// code below is good
//Use(std::dynamic_pointer_cast<Common::CDerived>(pData));
}
}
3. 返回智能指针管理的原生指针
namespace case3
{
/*
* after #ReturnRawPointer() and #GetRawPointer() returned, shared_ptr's destructor is called,
* then the returned raw pointer points to a deleted address. bang!
*/
static Common::CDerived* ReturnRawPointer()
{
auto pData = std::make_shared<Common::CDerived>();
return pData.get();
}
static bool GetRawPointer(Common::CDerived* & pReturnedData)
{
auto pData = std::make_shared<Common::CDerived>();
pReturnedData = pData.get();
return (pReturnedData != nullptr);
}
static void Entry()
{
auto pData = ReturnRawPointer();
pData->DoSomething();
pData = nullptr;
GetRawPointer(pData);
pData->DoSomething();
}
}
4. 类中的成员变量指针交给外部智能指针管理
namespace case4
{
/*
* CTest::pData is managed by CTest, but when call #Use(), it is used to construct a shared_pt,
* then it is also managed by a shared_ptr. double free!
*/
class CTest
{
public:
CTest() { pData = new Common::CDerived(); }
~CTest() { delete pData; }
Common::CDerived* pData;
};
/* this function need a shared_ptr, this is ok.*/
static void Use(std::shared_ptr<Common::CDerived> pData)
{
pData->DoSomething();
}
static void Entry()
{
auto pTest = std::make_shared<CTest>();
Use(std::shared_ptr<Common::CDerived>(pTest->pData));
}
}
5. 栈变量的地址交给智能指针管理
namespace case5
{
/*
* data is on stack, after managed by a shared_ptr, it will be deleted.
* we CAN NOT delete an address on stack!
*/
static void Use(std::shared_ptr<Common::CDerived> pData)
{
pData->DoSomething();
}
static void Entry()
{
// well, I have simplify this error quite much.
Common::CDerived data;
auto pData = std::shared_ptr<Common::CDerived>(&data);
Use(pData);
}
}
6. 循环引用导致的内存泄漏
#pragma once
#include <memory>
#include <vector>
#include "common.h"
namespace case6
{
class CPerson
{
public:
CPerson() { printf(__FUNCTION__ "\n"); }
virtual ~CPerson() { printf(__FUNCTION__ "\n"); }
};
class CParent : public CPerson
{
public:
CParent() { printf(__FUNCTION__ "\n"); }
virtual ~CParent() { printf(__FUNCTION__ "\n"); }
std::vector<std::shared_ptr<CPerson>> children;
};
class CChild : public CPerson
{
public:
CChild() { printf(__FUNCTION__ "\n"); }
virtual ~CChild() { printf(__FUNCTION__ "\n"); }
std::shared_ptr<CPerson> parent;
};
static void Entry()
{
// Oops, no one will be died then.
std::shared_ptr<CParent> parent = std::make_shared<CParent>();
std::shared_ptr<CChild> child1 = std::make_shared<CChild>();
std::shared_ptr<CChild> child2 = std::make_shared<CChild>();
std::shared_ptr<CChild> child3 = std::make_shared<CChild>();
parent->children.push_back(child1);
parent->children.push_back(child2);
parent->children.push_back(child3);
child1->parent = parent;
child2->parent = parent;
child3->parent = parent;
}
}
示例代码
完整的示例代码下载地址
CSDN:https://download.csdn.net/download/xiaoyanilw/13203123
百度云:https://pan.baidu.com/s/1dFUKevDXJZfja3HyO-jazg 提取码: 8ra8
总结
以上示例代码虽然有的看起来非常不可思议,这是我简化后的结果,在实际代码中经常以另外一种形式出现,一不小心就容易中招。