智能指针
Mzy 金丹

智能指针

智能指针的作用

C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。

理解智能指针需要从下面三个层次:

  1. 从较浅的层面看,智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。
  2. 智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。
  3. 智能指针还有一个作用是把值语义转换成引用语义

智能指针和管理的对象分别在哪个区

智能指针本身在栈区,托管的资源在堆区,利用了栈对象超出生命周期后自动析构的特征,所以无需手动delete释放资源。

智能指针对象位于栈内存,管理一个堆内存的对象,当智能指针对象的生命周期结束后,会在析构函数中释放掉管理的堆内存资源,从而防止堆内存对象内存泄漏。

img

unique_ptr能否被另一个unique_ptr拷贝呢?

  • 不能,因为它把它的拷贝构造函数private了。但是它提供了一个移动构造函数,所以可以通过std::move将指针指向的对象交给另一个unique_ptr,转交之后自己就失去了这个指针对象的所有权,除非被显示交回。

unique_ptr = unique_ptr和shared_ptr=shared_ptr这两个操作有什么后果呢?

对于unique_ptr` :

  • 这个操作是不允许的,因为unique_ptr它的原理是将拷贝构造和拷贝赋值私有化,但是它提供了移动构造和移动赋值。所以如果你想要使用=赋值,必须先把右边的用std::move包裹一下,这样右边的unique_ptr就会失去所有权,左边的unique_ptr就会得到对应对象的所有权

至于shared_ptr

  • 对于左边的指针,它会将自己的引用计数减一,然后检测一下是不是减到了0,如果是,那么delete所管理的对象
  • 然后将右边的引用计数和管理对象赋值给左边,此时两边指向同一个对象,共享同一个引用计数,然后引用计数++

shared_ptr的移动赋值时发生了什么事情

  • 首先它会检查本指针和参数指针是不是同一个对象,如果是,直接返回
  • 然后,先把本指针的引用变量–1,如果发现减到了0,就把参数指针和参数引用变量析构掉并置NULL
  • 最后,本指针和参数指针指向同一个对象以及引用计数,然后引用计数自增1

unique_ptr和shared_ptr的区别

  • unique_ptr代表的是专属所有权,不支持复制和赋值。但是可以移动

  • shared_ptr 代表的是共享所有权,shared_ptr 是支持复制的和赋值以及移动的

  • unique_ptr 在默认情况下和裸指针的大小是一样的。
    所以 内存上没有任何的额外消耗,性能是最优的,我们大多数场景下用到的应该都是 unique_ptr

  • shared_ptr 的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数。因此相比于 unique_ptr, shared_ptr 的内存占用更高。在使用 shared_ptr 之前应该考虑,是否真的需要使用 shared_ptr, 而非 unique_ptr

    shared_ptr你是怎么实现的

    shared_ptr 内部是利用引用计数来实现内存的自动管理,每当复制一个 shared_ptr,引用计数会 + 1。当一个 shared_ptr离开作用域时,引用计数会 - 1。当引用计数为 0 的时候,则 delete 内存。

    shared_ptr是不是线程安全

    • 不是

    • 引用计数的增减是原子操作没问题,但是shared_pytr的读写本身不只包括引用计数操作,还包括资源所有权的操作,这两个操作合起来不是原子的

    • 如果要求线程安全必须加锁

    • 资源所有权操作不是线程安全的:包括指针的赋值和删除操作,如果在多线程环境中使用,需要加锁保护。

      加锁保护资源所有权操作:在多线程环境中对 shared_ptr 的赋值或修改操作应使用互斥锁(如 std::mutex)来保护,以确保线程安全。

    weak_ptr如何检测指针是否被销毁

    expired()

    • 判断强引用计数是否为0
    • 如果返回true,那么被观测的对象(也就是shared_ptr管理的资源)已经不存在了

    如何将weak_ptr转换为shared_ptr

    lock():

    • 如果expired()为true,返回一个空shared_ptr,否则返回非空shared_ptr

智能指针的设计与实现

智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至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
#include <iostream>
#include <memory>

template<typename T>
class SmartPointer {
private:
T* _ptr;
atomic_int* _count;
public:
SmartPointer(T* ptr = nullptr) :
_ptr(ptr) {
if (_ptr) {
_count = new size_t(1);
} else {
_count = new size_t(0);
}
}
// 拷贝构造函数
SmartPointer(const SmartPointer& ptr) {
if (this != &ptr) {
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
}
}
//拷贝赋值
SmartPointer& operator=(const SmartPointer& ptr) {
if (this->_ptr == ptr._ptr) {
return *this;
}

if (this->_ptr) {
(*this->_count)--;
if (this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}

this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
return *this;
}
// 解引用
T& operator*() {
assert(this->_ptr != nullptr); // 断言指针不为空
return *(this->_ptr);

}

T* operator->() {
assert(this->_ptr == nullptr);
return this->_ptr;
}

~SmartPointer() {
(*this->_count)--;
if (*this->_count == 0) {
delete this->_ptr;
delete this->_count;
}
}

size_t use_count(){
return *this->_count;
}
};

int main() {
{
SmartPointer<int> sp(new int(10));
SmartPointer<int> sp2(sp);
SmartPointer<int> sp3(new int(20));
sp2 = sp3;
std::cout << sp.use_count() << std::endl;
std::cout << sp3.use_count() << std::endl;
}
//delete operator
}

shared_ptr

shared_ptr使用了引用计数,每一个shared_ptr的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1,每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr析构的时候,内存才会释放。

关于shared_ptr有几点需要注意:

• 不要用一个裸指针初始化多个shared_ptr,会出现double_free导致程序崩溃

• 通过shared_from_this()返回this指针,不要把this指针作为shared_ptr返回出来,因为this指针本质就是裸指针,通过this返回可能会导致重复析构,不能把this指针交给智能指针管理。

1
2
3
4
5
6
class A {
shared_ptr<A> GetSelf() {
return shared_from_this();
// return shared_ptr<A>(this); 错误,会导致double free
}
};
  • 尽量使用make_shared,少用new
  • 不要delete get()返回来的裸指针。
  • 不是new出来的空间要自定义删除器。
  • 要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。
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
#include<iostream>
#include<memory>
using namespace std;
struct ClassWrapper {
ClassWrapper() {
cout << "construct" << endl;
data = new int[10];
}
~ClassWrapper() {
cout << "deconstruct" << endl;
if (data != nullptr) {
delete[] data;
}
}
void Print() {
cout << "print" << endl;
}
int* data;
};

void Func(std::shared_ptr<ClassWrapper> ptr) {
ptr->Print();
}

int main() {
auto smart_ptr = std::make_shared<ClassWrapper>();
auto ptr2 = smart_ptr; // 引用计数+1
ptr2->Print();
Func(smart_ptr); // 引用计数+1
smart_ptr->Print();
ClassWrapper *p = smart_ptr.get(); // 可以通过get获取裸指针
p->Print();
return 0;
}
1
2
3
4
5
6
construct
print
print
print
print
deconstruct

weak_ptr

weak_ptr是用来监视shared_ptr的生命周期,它不管理shared_ptr内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视shared_ptr中管理的资源是否存在,可以用来返回this指针和解决循环引用问题。

  • 作用1:返回this指针,上面介绍的shared_from_this()其实就是通过weak_ptr返回的this指针。
  • 作用2:解决循环引用问题。

空悬指针问题:有两个指针p1p2,指向堆上的同一个对象Objectp1p2位于不同的线程中。假设线程A通过p1指针将对象销毁了(尽管把p1置为了NULL),那p2就成了空悬指针。

weak_ptr不控制对象的生命期,但是它知道对象是否还活着。如果对象还活着,那么它可以提升为有效的shared_ptr(提升操作通过lock()函数获取所管理对象的强引用指针);如果对象已经死了,提升会失败,返回一个空的shared_ptr

1)operator=():重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。
2)swap(x):其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。
3)reset():将当前 weak_ptr 指针置为空指针。
4)use_count():查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。
5)expired():判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)。
6)lock():如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。

unique_ptr

std::unique_ptr是一个独占型的智能指针,它不允许其它智能指针共享其内部指针,也不允许unique_ptr的拷贝和赋值。使用方法和shared_ptr类似,区别是不可以拷贝

Powered by Hexo & Theme Keep
Unique Visitor Page View