(C++) 内类生成智能指针shared_from_this介绍

😁介绍

自C++11起,有三大智能指针:

  • unique_ptr
  • shared_ptr
  • weak_ptr

都是内存管理中的非常重要的一部分动态内存管理 - cppreference.com

其中shared_ptr在实际应用中具有非常广泛的应用。而拷贝操作也是非常常见和重要的操作。在类外可以直接使用默认的拷贝构造和拷贝赋值,而类内呢?显然这两种默认的拷贝操作均不适用。

本文就是讲解使用std::enable_shared_from_this::shared_from_this()来处理该问题。

🤔类外操作

😅错误操作

使用原始指针构造。

这是一种非常经典的错误。虽然构造出的智能指针都被多个smart_ptr掌管了,但是并非同一个控制块来操作,是随着每次构造都有一个控制块。

因此下方示例代码会产生两次析构。严重情况下可以让项目程序直接崩溃。

#include <iostream>
#include <memory>

struct Node {
    ~Node() {
        std::printf("[%p] %s\n", this, __func__);
    }
};

void test(Node* p) {
    // errro 同一个对象析构了两次
    std::shared_ptr<Node> sp1(p);
    std::shared_ptr<Node> sp2(p);
}

int main() {
    Node* p = new Node{};
    test(p);

    std::printf("[%u] %s end\n", __LINE__, __func__);
}

😂正确操作

直接使用拷贝操作。

对于shared_ptr默认的拷贝操作,可以由多个shart_ptr都指向同一个对象,并由同一个控制块维护。

#include <iostream>
#include <memory>

struct Node {
    ~Node() {
        std::printf("[%p] %s\n", this, __func__);
    }
};

void test(Node* p) {
    std::shared_ptr<Node> sp1(p);
    // 执行拷贝构造,同时维护同一个p
    std::shared_ptr<Node> sp2 = sp1;
}

int main() {
    Node* p = new Node{};
    test(p);

    std::printf("[%u] %s end\n", __LINE__, __func__);
}

🤔类内操作

😮std::enable_shared_from_this<>

😮奇异递归模板 CRTP(Curiously Recurring Template Pattern)

CRTP是一种模板元编程技术,它允许一个类模板通过基类的形式捕获自身的类型。

这种模式的名字来源于其自身的递归使用,“Curiously” 指的是这种模式的非直观性,因为在通常情况下,基类通常是在类定义之后才被引用的,而在这个模式中,基类是在类定义的开始就引用了尚未完全定义的类。

😮基本原理

CRTP的基本原理是利用模板类和继承。在CRTP中,一个类通过继承一个模板类,并将自身作为模板参数传递给基类模板。这样做的好处是可以在派生类中使用基类模板中的成员,而这些成员依赖于派生类的类型。

😮std::enable_shared_from_this<>

std::enable_shared_from_this 能让其一个对象(假设其名为 t,且已被一个 std::shared_ptr 对象 pt 管理)安全地生成其他额外的 std::shared_ptr 实例(假设名为 pt1, pt2, ...),它们都与 pt 共享对象 t 的所有权。

其中有两个核心函数:

  • shared_from_this
  • weak_from_this()
#include <memory>
// 奇异递归模板 CRTP(Curiously Recurring Template Pattern)
struct Node : public std::enable_shared_from_this<Node> { };

😅错误操作

😅shared_ptr 经典错误

还是先展示一个经典错误。

由于在类内的成员函数,(没有额外操作的情况下)只能访问到this指针。

#include <iostream>
#include <memory>

struct Node {
    ~Node() {
        std::printf("[%p] %s\n", this, __func__);
    }

    std::shared_ptr<Node> make_this_share_ptr() {
        // 经典错误
        return std::shared_ptr<Node>(this);
    }
};

void test(Node* p) {
    std::shared_ptr<Node> sp1 = p->make_this_share_ptr();
    std::shared_ptr<Node> sp2 = p->make_this_share_ptr();
}

int main() {
    Node* p = new Node{};
    test(p);

    std::printf("[%u] %s end\n", __LINE__, __func__);
}

😅enable_shared_from_this 经典错误

回到本文的重头戏,这里使用奇异递归模板的方式继承std::enable_shared_from_this<>,并使用shared_from_this()创建出指向自己的share_ptr。

但如果直接使用,还是会出现一个经典错误。

#include <iostream>
#include <memory>

struct Node : public std::enable_shared_from_this<Node> {
    ~Node() {
        std::printf("[%p] %s\n", this, __func__);
    }

    std::shared_ptr<Node> make_this_share_ptr() {
        return shared_from_this();
    }
};

void test(Node* p) {
    // 事先没有创建任何`std::shared_ptr`实例
    std::shared_ptr<Node> sp = p->make_this_share_ptr();
}

int main() {
    Node* p = new Node{};
    test(p);

    std::printf("[%u] %s end\n", __LINE__, __func__);
}

即:在没有使用构造出std::shared_ptr,就直接调用了shared_from_this(),会抛出异常。

ref: 若在先前未由 std::shared_ptr 共享的对象上调用 shared_from_this,则抛出 std::bad_weak_ptr

在mingw-gcc中输出如下:

terminate called after throwing an instance of 'std::bad_weak_ptr'
what():  bad_weak_ptr

😂正确操作

正确操作即为先构造出一个std::shared_ptr再使用shared_from_this()

保证了前提安全后,不论是std::shared_ptr还是裸指针去操作shared_from_this()都不会出现问题。

#include <iostream>
#include <memory>

struct Node : public std::enable_shared_from_this<Node> {
    ~Node() {
        std::printf("[%p] %s\n", this, __func__);
    }

    std::shared_ptr<Node> make_this_share_ptr() {
        return shared_from_this();
    }
};

void test(Node* p) {
    // safe
    std::shared_ptr<Node> sp0(p);
    std::shared_ptr<Node> sp1 = sp0;
    std::shared_ptr<Node> sp2 = sp1->make_this_share_ptr();
    std::shared_ptr<Node> sp3 = p->make_this_share_ptr();
}

int main() {
    Node* p = new Node{};
    test(p);

    std::printf("[%u] %s end\n", __LINE__, __func__);
}

😁END

ref

关注我

关注我,学习更多C/C++,算法,计算机知识

B站:

👨‍💻主页:天赐细莲 bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值