文章目录
- 😁介绍
- 🤔类外操作
- 😅错误操作
- 😂正确操作
- 🤔类内操作
- 😮std::enable_shared_from_this<>
- 😮奇异递归模板 CRTP(Curiously Recurring Template Pattern)
- 😮基本原理
- 😮std::enable_shared_from_this<>
- 😅错误操作
- 😅shared_ptr 经典错误
- 😅enable_shared_from_this 经典错误
- 😂正确操作
- 😁END
😁介绍
自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);// 执行拷贝构造,同时维护同一个pstd::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) {// safestd::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
- std::shared_ptr - cppreference.com
- std::enable_shared_from_this - cppreference.com
关注我
关注我,学习更多C/C++,算法,计算机知识
B站:
👨💻主页:天赐细莲 bilibili