【智能指针】std::unique_ptr 和weak_ptr 基本用法

std::unique_ptr 

std::unique_ptr 是一种独占的语义,即只允许一个智能指针引用裸指针,这区别于 std::shared_ptr 允许多个 shared_ptr 引用同一个裸指针,它没有引用计数,它的性能比 shared_ptr 会高一点。

在用法上 std::unique_ptr 和 std::shared_ptr 是类似的,主要的不同是 std::unique_ptr 之间的赋值需要通过 std::move 实现。

在 code2 目录下新建一个 code5.cpp 文件:

#include <iostream>
#include <memory>
#include <functional>

struct A{
    ~A(){
        std::cout<<"destruct A"<<std::endl;
    }
};

int main(){

    std::unique_ptr<int> p(new int(1));
    std::cout<<*p<<std::endl;

    {
        std::unique_ptr<A> p(new A);
        std::unique_ptr<A> p1 = std::move(p);
    }

    {
        std::unique_ptr<A[]> p(new A[3]);
    }

    {
        std::unique_ptr<A, std::function<void(A*)>> p(new A, [](A* ptr){
            std::cout << "delete from a custom deleter...\n";
            delete ptr;
        });
    }
}

编译和运行代码:在 build 目录下执行

g++ ../code5.cpp -o code5 -std=c++11 && ./code5

输出结果:

1
destruct A
destruct A
destruct A
destruct A
delete from a custom deleter...
destruct A

构造 unique_ptr 的时候第二个参数是一个自定义删除器,如果不填写自定义删除器,就会使用默认的删除器,一般情况下我们用默认的删除器就可以了,如果有需要也可以写自定义的删除器。注意 C++11 中构造 std::unique_ptr 不能像 std::shared_ptr 那样(通过 make_shared 创建)通过 make_unique 方式去创建,make_unique 在 C++14 中才提供,当然也可以自己实现一个,具体实现这里不再赘述。

std::weak_ptr

std::weak_ptr 是用来监视 std::shared_ptr 的,通过 weak_ptr 就可以得知它监视的 shared_ptr 是否已经销毁了。

在 code2 目录下新建一个 code6.cpp 文件:

#include <iostream>
#include <memory>

std::weak_ptr<int> gw;

void f()
{
    if (auto spt = gw.lock()) { // 使用之前必须复制到 shared_ptr
        std::cout << *spt << "\n";
    }
    else {
        std::cout << "gw is expired\n";
    }
}

int main()
{
    {
        auto sp = std::make_shared<int>(42);
        gw = sp;

        f();
    }

    f();
}

编译和运行代码:在 build 目录下执行

g++ ../code6.cpp -o code6 -std=c++11 && ./code6

输出结果:

42
gw is expired

std::weak_ptr 需要通过 std::shared_ptr 构造,当所监视的 shared_ptr 析构之后,通过 weak_ptr 的 lock 方法返回的是一个 nullptr ,如果 shared_ptr 仍然存在则返回 shared_ptr 。

https://www.shiyanlou.com/courses/1414/learning/?id=14993

通过 shared_ptr 保证安全地回调

shared_ptr 的一个重要作用就是在回调的时候是安全的,因为回调的时候,该回调函数的宿主对象可能已经被销毁了,那么在后面某个时刻回调的时候,实际上该对象已经不存在了,这时候再执行回调函数就是错误的。通过 shared_ptr 可以解决这个问题,保证安全地回调。

本小结内容可以先无脑动手做一下,到了实验四的时候就可以理解这样做的好处了。

在 code2 目录下新建一个 code7.cpp 文件:

#include <iostream>
#include <memory>
#include <functional>

class person : public std::enable_shared_from_this<person> {

public:
    void update(){
        name_ = "jack";
        callback_();
    }

    void init_callback(){
        callback_ = [this]{
            test();
        };
    }

    void test(){
        auto self = this->shared_from_this();
        std::cout<<name_<<std::endl;
    }

private:
    std::function<void()> callback_;
    std::string name_ = "tom";
};

int main(){
    person* ptr = nullptr;

    {
        std::shared_ptr<person> p = std::make_shared<person>();
        p->init_callback();

        ptr = p.get();
    }

    ptr->update();
}

编译和运行代码:在 build 目录下执行

g++ ../code7.cpp -o code7 -std=c++11 && ./code7

输出结果:

terminate called after throwing an instance of 'std::bad_weak_ptr'
  what():  bad_weak_ptr
[1]    209 abort (core dumped)  ./code7

会出现一个运行错误,因为在调用 ptr->update() 时,该 ptr 已经被前面的 shared_ptr 释放了,这时候这个 ptr 实际上是无效的。

为了解决这个问题,我们可以通过派生于 std::enable_shared_from_this 来通过对象自身得到 shared_ptr ,然后在设置回调的 lambda 中捕获该 shared_ptr ,这样,lambda 表达式中的 shared_ptr 和 lambda 回调函数的生命周期一致了。

在 code2 目录下新建一个 code8.cpp 文件:

#include <iostream>
#include <memory>
#include <functional>

class person : public std::enable_shared_from_this<person> {

public:
    void update(){
        name_ = "jack";
        callback_();
    }

    void init_callback(){
        auto self = this->shared_from_this();
        callback_ = [this, self]{
            test();
        };
    }

    void test(){
        auto self = this->shared_from_this();
        std::cout<<name_<<std::endl;
    }

private:
    std::function<void()> callback_;
    std::string name_ = "tom";
};

int main(){
    person* ptr = nullptr;

    {
        std::shared_ptr<person> p = std::make_shared<person>();
        p->init_callback();

        ptr = p.get();
    }

    ptr->update();
}

编译和运行代码:在 build 目录下执行

g++ ../code8.cpp -o code8 -std=c++11 && ./code8

输出结果:

jack

关键的一句是 lambda 捕获了 shared_ptr ,即使外面的 shared_ptr 重置了,但是 lamdba 表达式中捕获的 shared_ptr 仍然存在,引用计数不会减为 0,可以保证回调回来的时候,该对象仍然存在。

尽量使用std::make_unique和std::make_shared而不直接使用new

让我们从对齐std::make_unique 和 std::make_shared这两块开始。std::make_shared是c++11的一部分,但很可惜std::make_unique不是。它是在c++14里加入标准库的。假如你在使用c++11,也别担心,你很容易写出一个基本的版本。看这里:

template<typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts&&... params)
{
    return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}

正如你看到的,make_unique完美传递了参数给对象的构造函数,从一个原始指针构造出一个std::unique_ptr,返回创建的std::unique_ptr。这个形式的函数不支持数组和定制删除器(见条款18),但它证明了一点点的努力就可以根据需要创建一个make_unique。要记住的是不要把你的版本放到std命名空间里,因为你不想当升级到c++14后会和库提供的标准实现冲突吧。

尽量使用的原因:https://blog.csdn.net/bandaoyu/article/details/112197053

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值