Smart Pointer

在C++11中通过引入智能指针的概念,使得C++程序员不需要手动释放内存

智能指针的种类

  • std::unique_ptr
  • std::shared_ptr
  • std::weak_ptr

概述

C++的指针包括两种

  • 原始指针(raw pointer)
  • 智能指针

智能指针是原始指针的封装,其优点是会自动分配内存,不用担心潜在的内存泄露。

unique_ptr

在任何给定的时刻,只能有一个指针管理内存

当指针超出作用域时,内存将自动释放

该类型指针不可Copy,只能Move

创建方式

通过已有裸指针创建

1
2
3
4
5
Cat *c_p2 = new Cat("p2");
std::unique_ptr<Cat> u_c_p2{c_p2};
// 建议销毁原始指针
c_p2 = nullptr;
u_c_p2->cat_info();

通过new来创建

1
2
3
4
std::unique_ptr<Cat> u_c_p3{new Cat("dd")};
u_c_p3->cat_info();
u_c_p3->set_cat_name("oo");
u_c_p3->cat_info();

通过std::make_unique创建

1
2
3
4
std::unique_ptr<Cat> u_c_p4 = std::make_unique<Cat>();
u_c_p4->cat_info();
u_c_p4->set_cat_name("oo");
u_c_p4->cat_info();

通过get()获取地址

函数调用

值传递

1
2
3
4
5
6
7
8
void do_with_cat_pass_value(std::unique_ptr<Cat> c) {
c->cat_info();
}

int main(int argc, char *argv[]) {
std::unique_ptr<Cat> c1 = std::make_unique<Cat>("ff");
do_with_cat_pass_value(c1);
}

报错

image-20240403110527123

1
2
3
4
5
6
7
8
void do_with_cat_pass_value(std::unique_ptr<Cat> c) {
c->cat_info();
}

int main(int argc, char *argv[]) {
std::unique_ptr<Cat> c1 = std::make_unique<Cat>("ff");
do_with_cat_pass_value(move(c1));
}

image-20240403110604947

1
2
3
4
5
6
7
8
9
void do_with_cat_pass_value(std::unique_ptr<Cat> c) {
c->cat_info();
}

int main(int argc, char *argv[]) {
std::unique_ptr<Cat> c1 = std::make_unique<Cat>("ff");
do_with_cat_pass_value(move(c1));
c1->cat_info();
}

报错,move将c1指向的资源转移。

image-20240403110653846

引用传递

1
2
3
4
5
6
7
8
9
10
11
12
void do_with_cat_pass_ref(std::unique_ptr<Cat> &c) {
c->set_cat_name("oo");
c->cat_info();
c.reset();
}

int main(int argc, char *argv[]) {
std::unique_ptr<Cat> c1 = std::make_unique<Cat>("ff");
// do_with_cat_pass_value(move(c1));
do_with_cat_pass_ref(c1);
std::cout << c1.get() << std::endl;
}

image-20240403111433907

shared_ptr

shared_ptr 计数指针又称共享指针,与unique_ptr不同的时它可以共享数据。

shared_ptr 创建了一个计数器与类对象所指的内存相关联,Copy则计数器加一,销毁则计数器减一。

1
2
3
4
5
6
7
8
int main(int argc, char *argv[]) {
std::shared_ptr<int> i_p_1 = std::make_shared<int>(10);
std::cout << "value : " << *i_p_1 << std::endl;
std::cout << "use count : "<< i_p_1.use_count() << std::endl;
std::shared_ptr<int> i_p_2 = i_p_1;
std::cout << "i_p_1 use count : " << i_p_1.use_count() << std::endl;
std::cout << "i_p_2 use count : " << i_p_2.use_count() << std::endl;
}

image-20240403112604782

shared_ptr与unique_ptr

不能将shared_ptr转换为unique_ptr。

unique_ptr可以转换为shared_ptr,通过std::move。

函数返回值推荐使用unique_ptr,因为可以根据需要随时转换为shared_ptr。

1
2
3
4
5
int main(int argc, char *argv[]) {
std::unique_ptr<Cat> c_p_1 = std::make_unique<Cat>("dd");
std::shared_ptr<Cat> c_p_2 = std::move(c_p_1);
std::cout << "c_p_2 use count : " << c_p_2.use_count() << std::endl;
}

image-20240403114006628

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::unique_ptr<Cat> get_unqiue_ptr() {
std::unique_ptr<Cat> p = std::make_unique<Cat>("local cat");
return p;
}

int main(int argc, char *argv[]) {
std::unique_ptr<Cat> c_p_1 = std::make_unique<Cat>("dd");
std::shared_ptr<Cat> c_p_2 = std::move(c_p_1);

std::shared_ptr<Cat> c_p_3 = get_unqiue_ptr();
if(c_p_3) {
c_p_3->cat_info();
std::cout << "c_p_3 use count : " << c_p_3.use_count() << std::endl;
}
}

image-20240403114215155

weak_ptr

1
2
3
4
5
6
7
8
int main(int argc, char *argv[]) {
std::shared_ptr<Cat> s_p_c1 = std::make_shared<Cat>("C1");
std::weak_ptr<Cat> w_p_c1{s_p_c1};

std::cout << "w_p_c1: " << w_p_c1.use_count() << std::endl;
std::cout << "s_p_c1: " << s_p_c1.use_count() << std::endl;

}

weak_ptr的创建不会使得计数器增加

image-20240403115010158

1
2
3
4
5
6
7
8
9
10
11
12
int main(int argc, char *argv[]) {
std::shared_ptr<Cat> s_p_c1 = std::make_shared<Cat>("C1");
std::weak_ptr<Cat> w_p_c1{s_p_c1};

std::cout << "w_p_c1: " << w_p_c1.use_count() << std::endl;
std::cout << "s_p_c1: " << s_p_c1.use_count() << std::endl;

std::shared_ptr<Cat> s_p_c2 = w_p_c1.lock();
std::cout << "w_p_c1: " << w_p_c1.use_count() << std::endl;
std::cout << "s_p_c1: " << s_p_c1.use_count() << std::endl;
std::cout << "s_p_c2: " << s_p_c2.use_count() << std::endl;
}

可以通过lock,将类型转换为shared_ptr。

image-20240403115224125

正常情况下c3,c4都会被销毁

1
2
std::shared_ptr<Cat> c3 = std::make_shared<Cat>("C3");
std::shared_ptr<Cat> c4 = std::make_shared<Cat>("C4");

image-20240403115912255

1
2
3
4
std::shared_ptr<Cat> c3 = std::make_shared<Cat>("C3");
std::shared_ptr<Cat> c4 = std::make_shared<Cat>("C4");
c3->set_friend(c4);
c4->set_friend(c3);

无法正常销毁,存在循环依赖问题。

image-20240403120921296

1
2
3
private:
std::string name{"Mimi"};
std::shared_ptr<Cat> m_friend;

将shard_ptr更换成weak_ptr即可正常销毁。

weak_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
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
#include <iostream>
#include "cat.h"

class share_count {
public:
share_count() : _count(1) {}
void add_count() {
++_count;
}
long reduce_count() {
return --_count;
}
long get_count() const {
return _count;
}
private:
long _count;
};

template<typename T>
class smart_ptr
{
public:
smart_ptr(T* ptr = NULL) : m_ptr(ptr) {
if (ptr) {
m_share_count = new share_count;
}
}
~smart_ptr() {
if (m_ptr && !m_share_count->reduce_count()) {
delete m_ptr;
delete m_share_count;
std::cout << "~smart_ptr" << std::endl;
}
}
T& operator*() const {return *m_ptr;}
T* operator->() const {return m_ptr;}
operator bool() const {return m_ptr;}

smart_ptr(const smart_ptr& rhs) noexcept {
m_ptr = rhs.m_ptr;
m_share_count = rhs.m_share_count;
m_share_count->add_count();
}

smart_ptr& operator=(const smart_ptr& rhs) noexcept {
m_ptr = rhs.m_ptr;
m_share_count = rhs.m_share_count;
m_share_count->add_count();
return *this;
}

long use_count() const {
if (m_ptr) {
return m_share_count->get_count();
}
return 0;
}
private:
T* m_ptr;
share_count* m_share_count;
};

int main() {
smart_ptr<Cat> sp1{new Cat("S")};
std::cout << "sp1 user count : " << sp1.use_count() << std::endl;
smart_ptr<Cat> sp2{sp1};
std::cout << "sp2 user cout : " << sp2.use_count() << std::endl;
std::cout << "sp1 user cout : " << sp1.use_count() << std::endl;
smart_ptr<Cat> sp3;
sp3 = sp2;
std::cout << "sp3 user cout : " << sp3.use_count() << std::endl;
std::cout << "sp2 user cout : " << sp2.use_count() << std::endl;
std::cout << "sp1 user cout : " << sp1.use_count() << std::endl;
}