C11智能指针shared_ptr、unique_ptr、weak_ptr

目的:实现堆内存的自动回收(垃圾回收机制)

智能指针可以在适当时机自动释放分配的内存。也就是说,使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现。

智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。

‍补充:

  • 内存泄漏(momory leak):
    • 是指程序在申请新的内存空间后,没有释放已经申请的内存空间,后果也许会造成内存溢出。
  • 内存溢出(out of memory):
    • 指程序申请内存时,没有足够的内存提供给申请者。内存不够用。

  • C++ 智能指针底层是采用引用计数的方式实现的。

  • 简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整型值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。

  • 当堆空间对应的整型值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉(自动执行delete p)


RAII机制

  • RAII机制(资源和对象绑定, 局部对象自动销毁)
    这种机制把资源的声明周期和对象的声明周期绑定, 存在的问题就是资源的当前使用者是唯一的, 出现赋值则情况需要特殊处理.

  • 在类的构造函数中申请资源并使用,最后在析构函数中释放资源


智能指针是利用了一种叫做**RAII(资源获取即初始化)**的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。


头文件

#include 

shared_ptr

  • 多个 shared_ptr 智能指针可以共同使用同一块堆内存。
  • 并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)。

创建

std::shared_ptr p1;             //不传入任何实参
std::shared_ptr p2(nullptr);    //传入空指针 nullptr
std::shared_ptr p3(new int(10));//
std::shared_ptr p3 = std::make_shared(10);//这两种方式创建的p3完全相同
//调用拷贝构造函数
std::shared_ptr p4(p3);//或者 std::shared_ptr p4 = p3;
//调用移动构造函数
std::shared_ptr p5(std::move(p4)); //或者 std::shared_ptr p5 = std::move(p4);

std::make_shared模板函数,其可以用于初始化 shared_ptr 智能指针

std::move(p4),该函数会强制将 p4 转换成对应的右值,因此初始化 p5 调用的是移动构造函数


‍‍‍同一普通指针不能同时为多个 shared_ptr 对象赋值,否则会导致程序发生异常

int* ptr = new int;
std::shared_ptr p1(ptr);
std::shared_ptr p2(ptr);//错误

‍‍‍不能将指针直接赋值给一个智能指针,一个是类,一个是指针。

例如

std::shared_ptr p4 = new int(1);

的写法是错误的


拷贝和赋值

  • 拷贝使得对象的引用计数加1
  • 赋值使得原对象引用计数减1
  • 当计数为0时,自动释放内存。
get() 获得 shared_ptr 对象内部包含的普通指针。
reset() 当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;
当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。
use_count() 返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。

#include 
#include 
using namespace std;

int main()
{
    //构建 2 个智能指针
    std::shared_ptr p1(new int(10));
    std::shared_ptr p2(p1);
    //输出 p2 指向的数据
    cout << *p2 << endl;
    p1.reset();//引用计数减 1,p1为空指针
    if (p1) {
        cout << "p1 不为空" << endl;
    }
    else {
        cout << "p1 为空" << endl;
    }
    //以上操作,并不会影响 p2
    cout << *p2 << endl;
    //判断当前和 p2 同指向的智能指针有多少个
    cout << p2.use_count() << endl;
    return 0;
}

运行结果

10
p1 为空
10
1

unique_ptr

每个 unique_ptr 指针都独自拥有对其所指堆内存空间的所有权。

这也就意味着,每个 unique_ptr 指针指向的堆内存空间的引用计数,都只能为 1,一旦该 unique_ptr 指针放弃对所指堆内存空间的所有权,则该空间会被立即释放回收。

创建

std::unique_ptr p1();
std::unique_ptr p2(nullptr);
std::unique_ptr p3(new int);
std::unique_ptr p4(new int);
std::unique_ptr p5(p4);//错误,堆内存不共享,没有拷贝构造函数
std::unique_ptr p5(std::move(p4));//正确,调用移动构造函数

对于调用移动构造函数的 p4 和 p5 来说,p5 将获取 p4 所指堆空间的所有权,而 p4 将变成空指针(nullptr)。

⭐基于 unique_ptr 类型指针不共享各自拥有的堆内存,因此 C++11 标准中的 unique_ptr 模板类没有提供拷贝构造函数,只提供了移动构造函数

在这里插入图片描述

‍‍‍不能赋值,不能拷贝

        //std::unique_ptr uptr2 = uptr;  //不能賦值
        //std::unique_ptr uptr2(uptr);  //不能拷貝
release() 释放当前 unique_ptr 指针对所指堆内存的所有权,但该存储空间并不会被销毁。
reset§ 其中 p 表示一个普通指针,如果 p 为 nullptr,则当前 unique_ptr 也变成空指针;
反之,则该函数会释放当前 unique_ptr 指针指向的堆内存(如果有),然后获取 p 所指堆内存的所有权(p 为 nullptr)。

基本操作

// 智能指针的创建
unique_ptr u_i; 	//创建空智能指针
u_i.reset(new int(3)); 	//绑定动态对象  
unique_ptr u_i2(new int(4));//创建时指定动态对象
unique_ptr u(d);	//创建空 unique_ptr,执行类型为 T 的对象,用类型为 D 的对象 d 来替代默认的删除器 delete

// 所有权的变化  
int *p_i = u_i2.release();	//释放所有权  
unique_ptr u_s(new string("abc"));  
unique_ptr u_s2 = std::move(u_s); //所有权转移(通过移动语义),u_s所有权转移后,变成“空指针” 
u_s2.reset(u_s.release());	//所有权转移
u_s2=nullptr;//显式销毁所指对象,同时智能指针变为空指针。与u_s2.reset()等价

#include 
#include 
using namespace std;
int main()
{
    std::unique_ptr p5(new int);
    *p5 = 10;
    // p 接收 p5 释放的堆内存
    int * p = p5.release();
    cout << *p << endl;
    //判断 p5 是否为空指针
    if (p5) {
        cout << "p5 is not nullptr" << endl;
    }
    else {
        cout << "p5 is nullptr" << endl;
    }
    std::unique_ptr p6;
    //p6 获取 p 的所有权
    p6.reset(p);
    cout << *p6 << endl;;
    return 0;
}

运行结果

10
p5 is nullptr
10

weak_ptr

该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用

借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。

  • 当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;
  • 同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。
  • 也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数

weak_ptr 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。

创建
std::weak_ptr wp1;
std::weak_ptr wp2 (wp1);
//下面是常用法
std::shared_ptr sp (new int);
std::weak_ptr wp3 (sp);
reset() 将当前 weak_ptr 指针置为空指针。
use_count() 查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。
expired() 判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)。
lock() 如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针(若返回 nullptr,则说明资源已经不存在,放弃对资源继续操作。);反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。

基本操作

weak_ptr w;	 	//创建空 weak_ptr,可以指向类型为 T 的对象
weak_ptr w(sp);	//与 shared_ptr 指向相同的对象,shared_ptr 引用计数不变。T必须能转换为 sp 指向的类型
w=p;				//p 可以是 shared_ptr 或 weak_ptr,赋值后 w 与 p 共享对象
w.reset();			//将 w 置空
w.use_count();		//返回与 w 共享对象的 shared_ptr 的数量
w.expired();		//若 w.use_count() 为 0,返回 true,否则返回 false
w.lock();			//如果 expired() 为 true,返回一个空 shared_ptr,否则返回非空 shared_ptr

#include 
#include 
using namespace std;
int main()
{
    std::shared_ptr sp1(new int(10));
    std::shared_ptr sp2(sp1);
    std::weak_ptr wp(sp2);
    //输出和 wp 同指向的 shared_ptr 类型指针的数量
    cout << wp.use_count() << endl;
    //释放 sp2
    sp2.reset();
    cout << wp.use_count() << endl;
    //借助 lock() 函数,返回一个和 wp 同指向的 shared_ptr 类型指针,获取其存储的数据
    cout << *(wp.lock()) << endl;
    return 0;
}

运行结果

2
1
10



循环引用问题

例子1

#include 
#include 
  
class Woman;  
class Man {
private:  
    //std::weak_ptr _wife;  
    std::shared_ptr _wife;  
public:  
    void setWife(std::shared_ptr woman) {  
        _wife = woman;  
    }  
  
    void doSomthing() {  
        if(_wife.lock()){}  
    }  
  
    ~Man() {
        std::cout << "kill man\n";  
    }  
};  
  
class Woman {  
private:  
    //std::weak_ptr _husband;  
    std::shared_ptr _husband;  
public:  
    void setHusband(std::shared_ptr man) {  
        _husband = man;  
    }  
    ~Woman() {  
        std::cout <<"kill woman\n";  
    }  
};

int main(int argc, char** argv) {  
    std::shared_ptr m(new Man());  
    std::shared_ptr w(new Woman());  
    if(m && w) {  
        m->setWife(w);  
        w->setHusband(m);  
    }  
    return 0;  
}

  • 在 Man 类内部会引用一个 Woman,Woman 类内部也引用一个 Man。
  • 当一个 man 和一个 woman 是夫妻的时候,他们直接就存在了相互引用问题。
  • man 内部有个用于管理wife生命期的 shared_ptr 变量,也就是说 wife 必定是在 husband 去世之后才能去世。
  • 同样的,woman 内部也有一个管理 husband 生命期的 shared_ptr 变量,也就是说 husband 必须在 wife 去世之后才能去世。

➡weak_ptr 对象引用资源时不会增加引用计数,但是它能够通过 lock() 方法来判断它所管理的资源是否被释放。

做法就是上面的代码注释的地方取消注释,取消 Woman 类或者 Man 类的任意一个即可,也可同时取消注释,全部换成弱引用 weak_ptr。

既然 weak_ptr 不增加资源的引用计数,那么在使用 weak_ptr 对象的时候,资源被突然释放了怎么办呢?

通过 weak_ptr 来间接访问资源➡ lock() 成员函数

答案是在需要访问资源的时候 weak_ptr 为你生成一个shared_ptr,shared_ptr 能够保证在 shared_ptr 没有被释放之前,其所管理的资源是不会被释放的。

创建 shared_ptr 的方法就是 lock() 成员函数。


例子2

struct A;
struct B;

struct A {
    std::shared_ptr pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};
struct B {
    std::shared_ptr pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};
int main() {
    auto a = std::make_shared();
    auto b = std::make_shared();
    a->pointer = b;
    b->pointer = a;
}

运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 `a,b`,这使得 `a,b` 的引用计数均变为了 2,而离开作用域时,`a,b` 智能指针被析构,却只能造成这块区域的引用计数减一,这样就导致了 `a,b` 对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露,如图 5.1:

C11智能指针shared_ptr、unique_ptr、weak_ptr_第1张图片


解决这个问题的办法就是使用弱引用指针 std::weak_ptrstd::weak_ptr是一种弱引用,不会引起引用计数增加。


C11智能指针shared_ptr、unique_ptr、weak_ptr_第2张图片

在上图中,最后一步只剩下 B,而 B 并没有任何智能指针引用它,因此这块内存资源也会被释放。




参考:

http://c.biancheng.net/view/7898.html

http://c.biancheng.net/view/vip_8672.html

http://c.biancheng.net/view/vip_8673.html

https://www.cnblogs.com/wxquare/p/4759020.html

https://mp.weixin.qq.com/s/xDagaZOfEFFEQVnSxByhcQ

https://blog.csdn.net/lyly_h/article/details/108312904

https://www.kancloud.cn/machh03/cpp11/2081459

https://wizardmerlin.github.io/posts/b43344a7/

https://blog.csdn.net/k346k346/article/details/81478223

https://veifi.com/?p=57

https://changkun.de/modern-cpp/zh-cn/05-pointers/

你可能感兴趣的:(c++,c++,c语言,c++11)