C++11 新特性 ⑥ | 智能指针 unique_ptr、shared_ptr 和 weak_ptr

目录

1、引言

2、unique_ptr

3、shared_ptr

4、weak_ptr

5、shared_ptr循环引用问题(面试题)


VC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/124272585C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/125529931C++软件分析工具从入门到精通案例集锦(专栏文章正在更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/article/details/131405795C/C++基础与进阶(专栏文章,持续更新中...)icon-default.png?t=N7T8https://blog.csdn.net/chenlycly/category_11931267.html       C++11新特性很重要,作为C++开发人员很有必要去了解去学习,不仅笔试面试时会涉及到,开源代码中会大规模的使用。以很多视频会议及直播软件都在使用的开源WebRTC项目为例,WebRTC代码中大篇幅地使用了C++11及以上的新特性,要读懂其源码,必须要了解这些C++的新特性。所以,接下来一段时间我将结合工作实践,给大家详细讲解一下C++11的新特性,以供借鉴或参考。

1、引言

       C++程序中使用的大部分内存都是动态申请来的堆内存,动态申请来的内存很容易出问题,要在正确的时间去释放内存没那么容易。有时我们如果忘记释放内存,就会产生内存泄漏(持续的内存泄漏会导致Out of memory内存耗尽);有时在尚有指针在引用内存的情况下我们就将内存释放了,就会产生引用非法内存的指针,就是我们常讲的野指针(或叫做悬空指针)。

       为了更容易、更安全地使用动态申请的内存,C++中引入了智能指针的概念。其实早在1998年发布的C++98标准中就引入了智能指针auto_ptr,不过auto_ptr有一些缺陷,现在已经被废弃了。C++11中引入了unique_ptr、shared_ptr和weak_ptr三个智能指针,位于C++标准STL库中,在 <memory> 头文件中的 std 命名空间中定义的。

       智能指针的大体机制和原理为:

C++智能指针用来存储指向动态分配(堆)对象指针的类,用于C++类对象的生存期的控制,确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每引用C++对象1次,内部引用计数就加1;智能指针每析构1次,内部的引用计数就减1,当引用计数减为0时,就会删除指向的C++类对象(释放类对象的堆内存)。

       C++11标准中主要使用unique_ptr和shared_ptr两智能指针,weak_ptr则主要用来辅助shared_ptr,避免出现循环引用问题的。在使用unique_ptr和shared_ptr两个智能指针类时,可以使用指针运算符(-> 和 *)访问指向的对象,因为智能指针类重载了->和*运算符,以返回指向的对象(指针),相关代码如下:

_NODISCARD add_lvalue_reference_t<_Ty> operator*() const
    {    // return reference to object
        return (*get());
    }
 
_NODISCARD pointer operator->() const noexcept
    {    // return pointer to class object
        return (this->_Myptr());
    }

其中,get接口的实现如下:

_NODISCARD pointer get() const noexcept
    {    // return pointer to object
        return (this->_Myptr());
    }

2、unique_ptr

       unique_ptr是独占式智能指针,它拥有对其所指向对象的唯一所有权。unique_ptr 指针被销毁时,它所指向的对象也会被销毁。由于其具有独占性,所以unique_ptr不能被拷贝,只能被转移所有权。将对象的所有权转移到新的unique_ptr对象中,原先的unique_ptr对象不再指向原来的对象。

       unique_ptr持有对对象的独有权,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。

       使用unique_ptr的实例如下:

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    unique_ptr<int> up1(new int(11));   // 无法复制的unique_ptr
    //unique_ptr<int> up2 = up1;        // error, 不能通过编译
    cout << *up1 << endl;               // 11

    unique_ptr<int> up3 = move(up1);    // 现在p3是数据的唯一的unique_ptr

    cout << *up3 << endl;     // 11
    //cout << *up1 << endl;   // error, 运行时错误
    up3.reset();              // 显式释放内存
    up1.reset();              // 不会导致运行时错误
    //cout << *up3 << endl;   // error, 运行时错误

    unique_ptr<int> up4(new int(22));   // 无法复制的unique_ptr
    up4.reset(new int(44));             // "绑定"动态对象
    cout << *up4 << endl;

    up4 = nullptr;           // 显式销毁所指对象,同时智能指针变为空指针。与up4.reset()等价

    unique_ptr<int> up5(new int(55));
    int *p = up5.release();  // 只是释放控制权,不会释放内存
    cout << *p << endl;
    //cout << *up5 << endl;  // error, 运行时错误
    delete p;                // 释放堆区资源

    return 0;
}

3、shared_ptr

       shared_ptr是共享式智能指针,一个对象可以被多个shared_ptr共享。每个shared_ptr内部都维护一个引用计数,当引用计数为 0 时,所指向的对象会被销毁。std::shared_ptr 可以被拷贝和移动。

       shared_ptr允许多个该智能指针共享第“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。

       使用shared_ptr的实例如下:

int main()
{
    shared_ptr<int> sp1(new int(22));
    shared_ptr<int> sp2 = sp1;

    cout << "count: " << sp2.use_count() << endl; // 打印引用计数

    cout << *sp1 << endl;   // 22
    cout << *sp2 << endl;   // 22

    sp1.reset();    // 显式让引用计数减1
    cout << "count: " << sp2.use_count() << endl; // 打印引用计数

    cout << *sp2 << endl;   // 22

    return 0;
}

4、weak_ptr

        weak_ptr是为了配合shared_ptr而引入的一种智能指针,它不具有普通指针的行为,没有重载*和->两个操作符,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。

        weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源 (也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr 获得一个可用的shared_ptr对象,从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。

       使用weak_ptr的实例如下:

void check(weak_ptr<int> &wp)
{
    shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
    if (sp != nullptr)
    {
        cout << "still " << *sp << endl;
    }
    else
    {
        cout << "pointer is invalid" << endl;
    }
}

int main()
{
    shared_ptr<int> sp1(new int(22));
    shared_ptr<int> sp2 = sp1;
    weak_ptr<int> wp = sp1; // 指向shared_ptr<int>所指对象

    cout << "count: " << wp.use_count() << endl; //打印计数器
    cout << *sp1 << endl;   // 22
    cout << *sp2 << endl;   // 22

    check(wp);              // still 22

    sp1.reset();
    cout << "count: " << wp.use_count() << endl;

    cout << *sp2 << endl;   // 22
    check(wp);              // still 22

    sp2.reset();
    cout << "count: " << wp.use_count() << endl;
    check(wp);              // pointer is invalid

    return 0;
}

5、shared_ptr循环引用问题(面试题)

       使用shared_ptr可能会出现循环引用问题,场景是两个类中都包含了指向对方的shared_ptr对象,这样会导致new出来的两个类没有走析构,引发内存泄漏问题。

       循环引用问题的示意图如下:

相关代码如下:

#include <iostream>
#include<memory>
 
using namespace std;
 
class B;
class A{
public:
    shared_ptr<B> bptr;
    ~A(){cout<<"~A()"<<endl;}
}
 
class B
{
public:
    shared_ptr<A> aptr;
    ~B( ){cout<<"~B()"<<endl;}
}

int main() 
{
    shared_ptr<A> pa(new A()); // 引用加1
    shared_ptr<B> pb(new B()); // 引用加1
    pa->bptr = pb; // 引用加1
    pa->aptr = pa; // 引用加1
    return 0;
}

       执行到上述return 0这句代码时,指向A和B两个对象的引用计数都是2。当退出main函数时,先析构shared_ptr<B> pb对象,B对象的引用计数减1,B对象的引用计数还为1,所以不会delete B对象,不会进入B对象析构函数,所以B类中的shared_ptr<A> aptr成员不会析构,所以此时A对象的引用计数还是2。当析构shared_ptr<A> pa时,A的引用计数减1,A对象的引用计数变为1,所以不会析构A对象。所以上述代码会导致A和B两个new出的对象都没释放,导致内存泄漏。

       为了解决上述问题,引入了weak_ptr,可以将类中包含的shared_ptr成员换成weak_ptr,如下:

相关代码如下:

#include <iostream>
#include<nemory>
 
using namespace std;
 
class B;
class A{
public:
    weak_ptr<B> bptr;  // 使用weak_ptr替代shared_ptr
    ~A(){cout<<"~A()"<<endl;}
}
 
class B
{
public:
    weak_ptr<A> aptr; // 使用weak_ptr替代shared_ptr
    ~B( ){cout<<"~B()"<<endl;}
}
 
int main() 
{
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());
    pa->bptr = pb;
    pa->aptr = pa;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/chenlycly/article/details/132787542
今日推荐