智能指针
为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针来管理动态对象。智能指针的行为类似常规指针,主要是自动释放所指向的对象。shared_pt
允许多个指针指向同一个对象;unique_ptr
则独占所指向的对象。
shared_ptr
- 当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。
- 当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存。
创建shared_ptr对象
1
2
3
std::shared_ptr<int> p1(new int());
std::shared_ptr<int> p1 = std::make_shared<int>();
std::shared_ptr<int> p1 = new int(); // 错误,因为shared_ptr内部是显示构造。
检查 shared_ptr 对象的引用计数
1
p1.use_count();
分离关联的原始指针
1
2
3
4
5
6
std::shared_ptr<int> p3 = p1; // 假如此时 p1.use_count = p3.use_count = 2。
p1.reset(); // 这种情况,p1 p1.use_count = 0(直接分离),而p3.use_count = 1,这是因为分离2个指针了,所以不共享引用计数。
// 如果没有这个p3,则p1.use_count = 0后,原始指针引用计数为0,直接会被delete。
p1.reset(new int(34)); // 这种情况,它将在内部指向新指针,p1.use_count = 1。
p1 = nullptr; // p1.use_count = 0。
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
struct Test {
std::string str; // 默认构造,str 是空字符串
~Test() { std::cout << "Test object with str = " << str << " destroyed" << std::endl; }
};
int main() {
std::shared_ptr<Test> p1 = std::make_shared<Test>();
p1->str = "1"; // 直接通过成员访问赋值
std::cout << "Reference count: " << p1.use_count() << std::endl;
std::shared_ptr<Test> p2 = p1; // 共享引用
p2->str = "2"; // 修改 str
std::cout << "Reference count after p2: " << p1.use_count() << std::endl;
p1.reset(); // p1 放弃引用
std::cout << "Reference count after reset: " << p1.use_count() << std::endl;
std::cout << "Reference count after reset: " << p2.use_count() << std::endl;
p2.reset(); // p2 也放弃引用
std::cout << "End of program" << std::endl;
return 0;
}
//结果
Reference count: 1
Reference count after p2: 2
Reference count after reset: 0
Reference count after reset: 1
Test object with str = 2 destroyed
End of program
当最后一个共享指针 std::shared_ptr
被销毁或重置时,所管理的对象才会被析构。p1 和 p2 共享对 Test 对象的所有权。当 p1.reset()
后,引用计数减为 1,但对象仍然由 p2 控制。当 p2.reset()
后,引用计数变为 0,此时 Test 对象被销毁,析构函数输出销毁信息。
跟以下代码同理
1
2
3
4
5
6
Blob<string> b1;
{
Blod<string> b2 = {"a", "an"};
b1 = b2;
}//b2被销毁了,但b2指向的元素没有被销毁
//b1指向最初由b2创建的元素
自定义删除器 Deleter
当 shared_ptr 对象指向数组
1
2
3
4
5
// 需要添加自定义删除器的使用方式
std::shared_ptr<int> p3(new int[12]); // 不能满足要求
// 指向数组的智能指针可以使用这种形式
std::shared_ptr<int[]> p3(new int[12]); // 正确使用方式
像这样申请的数组,应该调用delete []释放内存,而 shared_ptr<int>
析构函数中默认delete并不能满足需求。 可以使用 shared_ptr<int[]>
形式,或添加自定义删除器。
3种方式:Function的形式、Class的形式、Lambda表达式的形式
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <memory>
using namespace std;
struct Sample
{
Sample() {
std::cout << "Sample\n";
}
~Sample() {
std::cout << "~Sample\n";
}
};
void deleter(Sample * x) // Function的形式
{
std::cout << "Function Deleter\n";
delete[] x;
}
class Deleter // Class的形式
{
public:
void operator() (Sample * x) {
std::cout<<"Class Deleter\n";
delete[] x;
}
};
int main()
{
// Function的形式,作为删除器
std::shared_ptr<Sample> p2(new Sample[3], deleter);
std::cout << "-------------------------------------\n";
// Class的形式,作为删除器
std::shared_ptr<Sample> p3(new Sample[3], Deleter());
std::cout << "-------------------------------------\n";
// Lambda表达式的形式,作为删除器
std::shared_ptr<Sample> p4(new Sample[3], [](Sample * x){
std::cout<<"Lambda Deleter\n";
delete[] x;
});
std::cout << "-------------------------------------\n";
return 0;
}
// 输出结果:
Sample
Sample
Sample
-------------------------------------
Sample
Sample
Sample
-------------------------------------
Sample
Sample
Sample
-------------------------------------
Lambda Deleter
~Sample
~Sample
~Sample
Class Deleter
~Sample
~Sample
~Sample
Function Deleter
~Sample
~Sample
~Sample
shared_ptr 相对于普通指针的优缺点
与普通指针相比,shared_ptr仅提供-> 、*和==运算符,没有+、-、++、–、[]等运算符。
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
#include<iostream>
#include<memory>
struct Sample {
void dummyFunction() {
std::cout << "dummyFunction" << std::endl;
}
};
int main()
{
std::shared_ptr<Sample> ptr = std::make_shared<Sample>();
(*ptr).dummyFunction(); // 正常
ptr->dummyFunction(); // 正常
// ptr[0]->dummyFunction(); // 错误方式
// ptr++; // 错误方式
// ptr--; // 错误方式
std::shared_ptr<Sample> ptr2(ptr);
if (ptr == ptr2) // 正常
std::cout << "ptr and ptr2 are equal" << std::endl;
return 0;
}
不要使用同一个原始指针多次构造 shared_ptr 创建多个 shared_ptr 的正常方法是使用一个已存在的shared_ptr 进行创建,而不是使用同一个原始指针进行创建。
1
2
3
4
5
6
7
8
9
10
int *num = new int(23);
std::shared_ptr<int> p1(num);
std::shared_ptr<int> p2(p1); // 正确使用方法
std::shared_ptr<int> p3(num); // 不推荐
std::cout << "p1 Reference = " << p1.use_count() << std::endl; // 输出 2
std::cout << "p2 Reference = " << p2.use_count() << std::endl; // 输出 2
std::cout << "p3 Reference = " << p3.use_count() << std::endl; // 输出 1
// 注意:离开作用域会报错,因为p1/p2释放了num,此时num成了悬空指针,而p3又是单独的引用计数,又会去释放一遍,因此会报错。
不要用栈中的指针构造 shared_ptr 对象 shared_ptr 默认的构造函数中使用的是delete来删除关联的指针,所以构造的时候也必须使用new出来的堆空间的指针。
1
2
3
4
5
6
7
8
int main()
{
int x = 12;
std::shared_ptr<int> ptr(&x);
return 0;
}
// 注意:当 shared_ptr 对象超出作用域调用析构函数delete,指针&x时会出错。因为用的是delete,栈上的指针不能用,只能堆上。
建议使用 make_shared 为了避免以上两种情形,建议使用 make_shared()<>
创建 shared_ptr 对象,而不是使用默认构造函数创建。
1
2
std::shared_ptr<int> ptr_1 = make_shared<int>();
std::shared_ptr<int> ptr_2(ptr_1);
另外不建议使用get()函数获取 shared_ptr 关联的原始指针,因为如果在 shared_ptr 析构之前手动调用了delete函数,同样会导致类似的错误。
shared_ptr实现
智能指针的大致实现原理就是在析构函数中,检查所引用对象的引用计数,如果引用计数为0,则真正释放该对象内存。
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <iostream>
class counter {
public:
counter() : count(0) {} // 默认构造函数,初始化 count 为 0
counter(int parCount) : count(parCount) {} // 带参数构造函数
void increaseCount() { count++; }
void decreaseCount() { count--; }
int getCount() { return count; }
private:
int count;
};
template <typename T>
class SmartPointer {
public:
// 默认构造函数,初始化为空
explicit SmartPointer() : mPtr(nullptr), pCounter(nullptr) {}
// 带参数构造函数,初始化指针和引用计数
explicit SmartPointer(T *pT) : mPtr(pT), pCounter(new counter(1)) {}
// 析构函数,减少引用计数,若计数为 0,则释放资源
~SmartPointer() {
if (pCounter == nullptr) {
return; // 如果引用计数为空,直接返回
}
pCounter->decreaseCount();
if (pCounter->getCount() == 0) {
delete pCounter;
delete mPtr;
pCounter = nullptr;
mPtr = nullptr;
}
}
// 拷贝构造函数
SmartPointer(const SmartPointer<T> &rh) {
this->mPtr = rh.mPtr;
this->pCounter = rh.pCounter;
if (pCounter) {
pCounter->increaseCount(); // 引用计数加 1
}
}
// 赋值运算符
SmartPointer<T> &operator=(const SmartPointer<T> &rh) {
if (this == &rh) {
return *this; // 如果是自赋值,直接返回
}
// 先减少旧对象的引用计数
if (pCounter) {
pCounter->decreaseCount();
if (pCounter->getCount() == 0) {
delete pCounter;
delete mPtr;
}
}
// 复制新的指针和引用计数
this->mPtr = rh.mPtr;
this->pCounter = rh.pCounter;
if (pCounter) {
pCounter->increaseCount(); // 引用计数加 1
}
return *this;
}
// 解引用运算符
T &operator*() { return *mPtr; }
// 成员访问运算符
T *operator->() { return mPtr; }
// 获取原始指针
T *get() { return mPtr; }
// 获取常量指针
const T* get() const { return mPtr; }
private:
T *mPtr; // 智能指针管理的原始指针
counter *pCounter; // 引用计数器
};
unique_ptr
- unique_ptr对象始终是关联的原始指针的唯一所有者。
- 我们无法复制unique_ptr对象,它只能移动。
- 由于每个unique_ptr对象都是原始指针的唯一所有者,因此在其析构函数中它直接删除关联的指针,不需要任何参考计数。
1
2
3
4
5
6
7
8
9
std::unique_ptr<int> ptr1;
std::unique_ptr<Task> taskPtr(new Task(22));
std::unique_ptr<Task> taskPtr = std::make_unique<Task>(34); // C++ 14 引入
std::unique_ptr<Task> taskPtr(new std::unique_ptr<Task>::element_type(23)); // 不常用
std::unique_ptr<Task> taskPtr2 = new Task(); // 编译错误,不能隐式构造,和shared_ptr一样
// 有几点需要注意:
// 第2行是完全可以理解的,因为unique_ptr有一个构造函数,其所需参数为裸指针。
// 第5行,如果等号左右两边是一样的类,那要么是拷贝构造,要么是拷贝赋值运算符,取决于写一行还是两行。
// 但如果等号左右不同,则需要先进行隐式转换,再进行拷贝(构造或赋值)。此时,如果其本身构造函数不允许隐式转换,则编译错误。
1
2
3
4
5
6
7
8
9
10
11
//获取被管理对象的指针
Task *p1 = taskPtr.get();
//重置 unique_ptr 对象
taskPtr.reset(); // delete原始关联指针,并taskPtr = nullptr置空,不需要考虑引用计数之类的。
//unique_ptr 对象不可复制
// 编译错误 : unique_ptr 不能复制
std::unique_ptr<Task> taskPtr3 = taskPtr2; // 拷贝构造函数,Compile error
// 编译错误 : unique_ptr 不能复制
taskPtr = taskPtr2; // 拷贝赋值运算符,compile error
1
2
3
4
5
6
7
8
// 通过原始指针创建 taskPtr2
std::unique_ptr<Task> taskPtr2(new Task(55));
// 通过std::move转移给 taskPtr4
std::unique_ptr<Task> taskPtr4 = std::move(taskPtr2);
// 现在taskPtr2 = nullptr
// std::move() 将把 taskPtr2 转换为一个右值引用。然后调用 unique_ptr 的移动构造函数,并将关联的原始指针传输到 taskPtr4。
// 在转移完原始指针的所有权后, taskPtr2将变为空。
可以返回unique_ptr
1
2
3
4
5
6
7
8
9
10
unique_ptr<int> clone(int p) {
unique_ptr<int> pInt(new int(p));
return pInt; // 返回unique_ptr
}
int main() {
int p = 5;
unique_ptr<int> ret = clone(p); // 其实和std::move转成右值后移动是一样的,函数返回的也是右值,可以调用移动构造函数。
cout << *ret << endl;
}
释放关联的原始指针
- 在 unique_ptr 对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。
- 这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。
reset() & release() & get()对比
- reset() 重置unique_ptr为空,delete其关联的指针。
- release() 返回关联指针,并将unique_ptr置空。(不delete关联指针,但取消关联指针的所有权)。
- get() 仅仅返回关联指针。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// unique_ptr::reset example
#include <iostream>
#include <memory>
int main () {
std::unique_ptr<int> up; // empty
up.reset(new int); // takes ownership of pointer
*up=5;
std::cout << *up << '\n';
up.reset(new int); // deletes managed object, acquires new pointer
*up=10;
std::cout << *up << '\n';
up.reset(); // deletes managed object
return 0;
}
Output:
5
10
实现
典型用途:
- 作为一个类的成员变量,这个变量只在本类使用,不会被赋值给其他类,也不会作为参数传递给某个函数。
- 在一个函数作为局部变量,使用完就不用再管,函数结束,自动释放托管资源。
原理:
- 构造时传入托管对象的指针,析构时delete对象
- 禁用赋值函数
实现:
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
#include <iostream>
#include <utility>
/****
* 智能指针unique_ptr的简单实现
*
* 特点:独享它指向的对象。也就是说,同时只有一个unique_ptr指向同一个对象,当这个unique_ptr被销毁时,指向的对象也随即被销毁
*
* 典型用途:
* 1. 在一个函数定义一个A* ptr = new A(),
* 结束还需要用delete,而用unique_ptr,就不需要自己调用delete
* 2.
* 作为一个类的变量,这个变量只在本类使用,不会被其他类调用,也不会作为参数传递给某个函数
* */
template <typename T> class unique_ptr {
private:
T *ptr_resource = nullptr;
public:
// explicit构造函数是用来防止隐式转换, 即不允许写成unique_ptr<T> tempPtr = T;
// std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能.
// move之后,raw_resource内部的资源将不能再被raw_resource使用
explicit unique_ptr(T *raw_resource) noexcept
: ptr_resource(std::move(raw_resource)) {}
unique_ptr(std::nullptr_t) : ptr_resource(nullptr) {}
unique_ptr() noexcept : ptr_resource(nullptr) {}
// 析构时, 释放托管的对象资源
~unique_ptr() noexcept { delete ptr_resource; }
// Disables the copy/ctor and copy assignment operator. We cannot have two
// copies exist or it'll bypass the RAII concept.
// 重要,禁止两种拷贝的赋值方式
// 使用"=delete"修饰,表示函数被定义为deleted,也就意味着这个成员函数不能再被调用,否则就会出错。
unique_ptr(const unique_ptr<T> &) noexcept = delete;
unique_ptr &operator=(const unique_ptr &) noexcept = delete;
public:
//&& 是右值引用,见https://zhuanlan.zhihu.com/p/107445960
// 允许移动语义。虽然无法复制unique_ptr,但可以安全地移动。
// 例子:unique_ptr<Test> tPtr3(std::move(tPtr1));
unique_ptr(unique_ptr &&move) noexcept {
std::cout << "construct for unique_ptr&&" << std::endl;
move.swap(*this);
}
// ptr = std::move(resource)
unique_ptr &operator=(unique_ptr &&move) noexcept {
std::cout << "operator= for unique_ptr&&" << std::endl;
move.swap(*this);
return *this;
}
explicit operator bool() const noexcept { return this->ptr_resource; }
// releases the ownership of the resource. The user is now responsible for
// memory clean-up.
T *release() noexcept { return std::exchange(ptr_resource, nullptr); }
// returns a pointer to the resource
T *get() const noexcept { return ptr_resource; }
// swaps the resources
void swap(unique_ptr<T> &resource_ptr) noexcept {
std::swap(ptr_resource, resource_ptr.ptr_resource);
}
// reset就删除老的,指向新的
void reset(T *resource_ptr) noexcept(false) {
// ensure a invalid resource is not passed or program will be terminated
if (resource_ptr == nullptr)
throw std::invalid_argument(
"An invalid pointer was passed, resources will not be swapped");
delete ptr_resource;
ptr_resource = nullptr;
std::swap(ptr_resource, resource_ptr);
}
public:
// overloaded operators
T *operator->() const noexcept { return this->ptr_resource; }
T &operator*() const noexcept { return *this->ptr_resource; }
// 额外说明noexcept
// noexcept C++11关键字,
// 告诉编译器,函数中不会发生异常,有利于编译器对程序做更多的优化
// C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化
};
#include "UniquePtr.h"
/**
* 简单的类,将被智能指针使用
* */
class Test {
public:
Test() { std::cout << "Test class construct" << std::endl; }
~Test() { std::cout << "Test class destruct" << std::endl; }
void printSomething() { std::cout << "Test printSomething " << std::endl; }
void printResource() { std::cout << "Test printResource " << a << std::endl; }
int getResource() { return a; }
private:
int a = 10;
};
/**
* 使用unique_ptr的类
* */
class PUser {
public:
PUser() {
// 初始化pTest
pTest.reset(new Test());
std::cout << "PUser construct " << std::endl;
}
~PUser() { std::cout << "PUser destruct" << std::endl; }
// 可以在类的各种函数,使用pTest,
void userTest() {
std::cout << "userTest " << pTest->getResource() << std::endl;
}
private:
// 典型用法,在一个类中,作为一个类成员变量
unique_ptr<Test> pTest;
};
/**
* 主程序入口
**/
int main(int argc, char *argv[]) {
unique_ptr<Test> tPtr1(new Test());
// 以下这两句话,//编译就不通过,因为已经定义, unique_ptr& operator = (const
// unique_ptr&) noexcept = delete; unique_ptr<Test> tPtr2 = tPtr1;
// unique_ptr<Test> tPtr3(tPtr1);
// 以下两句话就允许,因为pPtr1做了控制权转移
unique_ptr<Test> tPtr3(std::move(tPtr1));
unique_ptr<Test> tPtr4 = std::move(tPtr3);
// tPtr1->printResource();//这一句就崩溃,因为tPtr1非空,只不过资源完全不能用了
tPtr1
->printSomething(); // 这一句不崩溃,tPtr1虽然资源不能用,但是代码段可以调用,只要代码段没有使用到资源
PUser *pUser = new PUser();
pUser->userTest();
return 0;
}
weak_ptr
- weak_ptr虽然是一个模板类,但是不能用来直接定义指向原始指针的对象。
- weak_ptr接受shared_ptr类型的变量赋值,但是反过来是行不通的,需要使用lock函数。
weak_ptr设计之初就是为了服务于shared_ptr的(用于解决循环引用),所以不增加引用计数就是它的核心功能。
由于不知道什么之后weak_ptr所指向的对象就会被析构掉,所以使用之前请先使用expired函数检测一下。
- 主要有lock、swap、reset、expired、operator=、use_count几个函数,与shared_ptr相比多了lock、expired函数,但是却少了get函数,甚至连operator* 和 operator->都没有。
不会增加引用计数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void test1()
{
// weak_ptr<CA> ptr_1(new CA()); // 报错,不能直接用weak_ptr接管原始指针
// shared_ptr<CA> ptr_3 = wk_ptr; // 报错,不能用weak_ptr给shared_ptr赋值,除非用lock();反过来可以
shared_ptr<CA> ptr_1(new CA());
cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 1
shared_ptr<CA> ptr_2 = ptr_1;
cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2
weak_ptr<CA> wk_ptr = ptr_1;
cout << "ptr_1 use count : " << ptr_1.use_count() << endl; // 输出:ptr_1 use count : 2
cout << "ptr_2 use count : " << ptr_2.use_count() << endl; // 输出:ptr_1 use count : 2 // 引用计数仍然为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
34
35
36
37
38
39
40
41
42
43
44
45
46
class CB;
class CA
{
public:
CA() { cout << "CA() called! " << endl; }
~CA() { cout << "~CA() called! " << endl; }
void set_ptr(shared_ptr<CB>& ptr) { m_ptr_b = ptr; }
void b_use_count() { cout << "b use count : " << m_ptr_b.use_count() << endl; }
void show() { cout << "this is class CA!" << endl; }
private:
shared_ptr<CB> m_ptr_b; // 类CA装着类CB的shared_ptr
};
class CB
{
public:
CB() { cout << "CB() called! " << endl; }
~CB() { cout << "~CB() called! " << endl; }
void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
void show() { cout << "this is class CB!" << endl; }
private:
shared_ptr<CA> m_ptr_a; // 类CB装着类CA的shared_ptr
};
void test_refer_to_each_other()
{
shared_ptr<CA> ptr_a(new CA());
shared_ptr<CB> ptr_b(new CB());
cout << "a use count : " << ptr_a.use_count() << endl;
cout << "b use count : " << ptr_b.use_count() << endl;
ptr_a->set_ptr(ptr_b); // 给各自内部的对方的shared_ptr赋值
ptr_b->set_ptr(ptr_a);
cout << "a use count : " << ptr_a.use_count() << endl;
cout << "b use count : " << ptr_b.use_count() << endl;
}
// 测试结果
CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 2
b use count : 2
可以看到最后CA和CB没有被析构。
起初定义完ptr_a和ptr_b时,只有①③两条引用,然后调用函数set_ptr后又增加了②④两条引用,当test_refer_to_each_other这个函数返回时,对象ptr_a和ptr_b被销毁,也就是①③两条引用会被断开,但是②④两条引用依然存在,每一个的引用计数都不为0,结果就导致其指向的内部对象无法析构,造成内存泄漏。
解决这种状况的办法就是将两个类中的一个成员变量改为weak_ptr对象,因为weak_ptr不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将CB中的成员变量改为weak_ptr对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class CB
{
public:
CB() { cout << "CB() called! " << endl; }
~CB() { cout << "~CB() called! " << endl; }
void set_ptr(shared_ptr<CA>& ptr) { m_ptr_a = ptr; }
void a_use_count() { cout << "a use count : " << m_ptr_a.use_count() << endl; }
void show() { cout << "this is class CB!" << endl; }
private:
weak_ptr<CA> m_ptr_a; // 将CB中的类CA的shared_ptr成员变量,改为weak_ptr对象
};
// 测试结果
CA() called!
CB() called!
a use count : 1
b use count : 1
a use count : 1
b use count : 2
~CA() called!
~CB() called!
通过这次结果可以看到,CA和CB的对象都被正常的析构了,引用关系如下图所示,流程与上一例子相似,但是不同的是④这条引用是通过weak_ptr建立的,并不会增加引用计数,也就是说CA的对象只有1个引用计数,而CB的对象只有2个引用计数,当test_refer_to_each_other这个函数返回时,对象ptr_a和ptr_b被销毁,也就是①③两条引用会被断开,此时CA对象的引用计数会减为0,对象被销毁,其内部的m_ptr_b成员变量也会被析构,导致CB对象的引用计数会减为0,对象被销毁,进而解决了引用成环的问题。
常用函数用法示例
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
void test2()
{
shared_ptr<CA> ptr_a(new CA()); // 输出:CA() called!
shared_ptr<CB> ptr_b(new CB()); // 输出:CB() called!
cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
weak_ptr<CA> wk_ptr_a = ptr_a;
weak_ptr<CB> wk_ptr_b = ptr_b;
if (!wk_ptr_a.expired())
{
wk_ptr_a.lock()->show(); // 输出:this is class CA!
}
if (!wk_ptr_b.expired())
{
wk_ptr_b.lock()->show(); // 输出:this is class CB!
}
// 编译错误
// 编译必须作用于相同的指针类型之间
// wk_ptr_a.swap(wk_ptr_b); // 调用交换函数
wk_ptr_b.reset(); // 将wk_ptr_b的指向清空
if (wk_ptr_b.expired())
{
cout << "wk_ptr_b is invalid" << endl; // 输出:wk_ptr_b is invalid 说明改指针已经无效
}
wk_ptr_b = ptr_b;
if (!wk_ptr_b.expired())
{
wk_ptr_b.lock()->show(); // 输出:this is class CB! 调用赋值操作后,wk_ptr_b恢复有效
}
// 编译错误
// 编译必须作用于相同的指针类型之间
// wk_ptr_b = wk_ptr_a;
// 最后输出的引用计数还是1,说明之前使用weak_ptr类型赋值,不会影响引用计数
cout << "ptr_a use count : " << ptr_a.use_count() << endl; // 输出:ptr_a use count : 1
cout << "ptr_b use count : " << ptr_b.use_count() << endl; // 输出:ptr_b use count : 1
}