智能指针避坑指南——几种常见的错误用法

前言

智能指针的出现大大减轻了 C++ 程序员的心理负担(最少对于我是这样的),不用再时时刻刻担心一个 new 出来的指针是否被 delete 的问题了。虽然智能指针很强大,但是如果用不好,还是会导致各种各样的问题。最近,在项目里看到了几种智能指针的典型错误用法。有的严重,有的轻,有的问题在研发阶段并没有立刻暴露出来,埋下了一颗定时炸弹。趁着这个机会,总结一下几种常见的错误用法。希望对各位小伙伴儿有帮助。

说明:智能指针有很多种,标准库提供了几种,比如最常用的 shared_ptrunique_ptr, 比较少用的 weak_ptr 以及被废弃的 auto_ptr。项目代码里还有一种基于引用计数的智能指针。本文只列举几种常见的错误用法,不会深入到各种智能指针的实现。如果想深刻理解智能指针的用法,一定要看源码。

错误用法

在列举各种错误用法之前,想说明一点:这些错误都是实际遇到过的,并不是凭空想出来的。所以,即使有些示例代码错的是那么明显,也请不要轻视。

1. 同一指针交给两个智能指针管理导致二次释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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. 错误的动态转换导致的二次释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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. 返回智能指针管理的原生指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

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. 类中的成员变量指针交给外部智能指针管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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. 栈变量的地址交给智能指针管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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. 循环引用导致的内存泄漏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#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

总结

以上示例代码虽然有的看起来非常不可思议,这是我简化后的结果,在实际代码中经常以另外一种形式出现,一不小心就容易中招。

BianChengNan wechat
扫描左侧二维码关注公众号,扫描右侧二维码加我个人微信:)
0%