文章目录
0. 概要
在C++编程:使用std::weak_ptr监控std::shared_ptr解决多线程竞态实例(智能指针失效)中我们提到了悬空指针(智能指针失效)的问题。
而 std::enable_shared_from_this
也可是避免在多线程环境中因对象提前销毁而导致的std::shared_ptr
悬空指针访问问题。
本文将详细介绍std::enable_shared_from_this
的使用方法、原理和注意事项。
1. 什么是 std::enable_shared_from_this
?
在现代 C++ 编程中,特别是在多线程环境下,std::enable_shared_from_this
是一个重要的工具。它是一个模板类,定义在 <memory>
头文件中,能够让一个对象在已经有一个 std::shared_ptr
指向它的情况下,通过 shared_from_this()
方法安全地获取额外的 std::shared_ptr
。这种方法有效地避免了因对象提前销毁而导致的悬空 std::shared_ptr
访问问题。
以下是来自cplusplus-enable_shared_from_this的介绍
- 模板类定义
template <class T> class enable_shared_from_this;
- 功能
该类提供的功能允许派生类的对象创建指向自身的shared_ptr
实例,并与现有的shared_ptr
对象共享所有权。
请注意,简单地返回 shared_ptr<T>(this)
是有问题的,因为这会创建一个不同的所有权组。
- 官方示例
#include <iostream>
#include <memory>
struct C : std::enable_shared_from_this<C> { };
int main () {
std::shared_ptr<C> foo, bar;
foo = std::make_shared<C>();
bar = foo->shared_from_this();
if (!foo.owner_before(bar) && !bar.owner_before(foo))
std::cout << "foo and bar share ownership";
return 0;
}
- 输出
foo and bar share ownership
2. std::enable_shared_from_this
的作用
std::enable_shared_from_this
的核心作用在于允许继承了它的类的对象,在已有 std::shared_ptr
管理时,安全地返回一个额外的 std::shared_ptr
。这种机制通常在生产者-消费者模型中非常有用,其中生产者创建对象并使用 std::shared_ptr
管理,消费者通过 std::weak_ptr
安全地访问这些对象。
来看下面的一段小程序:
#include <iostream>
#include <memory>
class Test {
public:
~Test() { std::cout << "Test Destructor." << std::endl; }
std::shared_ptr<Test> GetObject() {
std::shared_ptr<Test> pTest(this);
return pTest;
}
};
int main() {
{
std::shared_ptr<Test> p(new Test());
std::shared_ptr<Test> q = p->GetObject();
}
return 0;
}
程序输出:
Test Destructor.
Test Destructor.
从上面的输出可以发现,我们只创建了一个 Test
对象,但却调用了两次析构函数。这对程序来说是灾难性的。为什么会出现这种情况呢?在 main
函数中,std::shared_ptr<Test> p(new Test());
将 shared_ptr
中引用计数器的值设置为 1,而在 GetObject
函数中,通过 std::shared_ptr<Test> pTest(this);
又将 shared_ptr
中引用计数器的值增加了 1。因此,在析构时,一个 Test
对象被析构了两次。即产生这个错误的原因是通过同一个 Test
指针对象创建了多个 shared_ptr
,这是绝对禁止的。
这也提醒我们在使用 shared_ptr
时,绝对不能通过同一个指针对象创建多个 shared_ptr
对象。那么,有什么方法从一个类的成员函数中获取当前对象的 shared_ptr
呢?其实方法很简单:只需要让该类继承自 enable_shared_from_this
模板类,然后在需要 shared_ptr
的地方调用 enable_shared_from_this
模板类的成员函数 shared_from_this()
即可。下面是改进后的代码:
#include <iostream>
#include <memory>
class Test : public std::enable_shared_from_this<Test> {
public:
~Test() { std::cout << "Test Destructor." << std::endl; }
std::shared_ptr<Test> GetObject() {
return shared_from_this();
}
};
int main() {
{
std::shared_ptr<Test> p(new Test());
std::shared_ptr<Test> q = p->GetObject();
}
return 0;
}
程序输出:
Test Destructor.
从输出结果可以看出,对象只被析构了一次,这是我们想要的结果。因此,enable_shared_from_this
模板类的作用是:作为一个基类,它允许从一个成员函数中获得当前对象的 shared_ptr
。
3. 解决悬空指针访问的问题
在C++编程:使用std::weak_ptr监控std::shared_ptr解决多线程竞态实例(智能指针失效)中我们提到了悬空指针的问题。
在生产者-消费者模型中的应用示例,假设 Event
结构体继承自 std::enable_shared_from_this<Event>
。在生产者线程中,可以这样创建和管理事件对象:
struct Event : std::enable_shared_from_this<Event> {
int id;
};
std::shared_ptr<Event> event = std::make_shared<Event>();
接着,生产者将事件对象添加到事件队列时,可以使用 shared_from_this()
返回的 std::shared_ptr
作为 std::weak_ptr
存入队列:
std::weak_ptr<Event> weak_event = event->shared_from_this();
event_queue.push_event(event, weak_event);
在消费者线程中,可以安全地使用 std::weak_ptr
,并通过 lock()
方法获取 std::shared_ptr
:
if (auto shared_ptr = weak_event.lock()) {
// 使用 shared_ptr 安全地访问事件对象
}
这种方式确保了消费者线程在使用事件对象时,不会因为对象提前被销毁而导致悬空指针访问的问题。
4. 无法解决的竞态条件
尽管 std::enable_shared_from_this
能够帮助在多线程环境中安全地获取 std::shared_ptr
,但它并不能解决所有的竞态条件问题。在生产者-消费者模型中,仍然需要使用适当的同步机制(如互斥量)来保护共享数据结构,以防止多个线程同时访问和修改数据而引发的竞态条件。
例如,考虑一个简单的生产者-消费者模式,其中多个生产者向队列中添加元素,而多个消费者从队列中取出元素。
假设有一个线程安全的队列类ThreadSafeQueue
,它包含一个std::deque
作为底层容器,并且生产者和消费者线程都使用std::shared_ptr
来管理ThreadSafeQueue
的生命周期。即使生产者和消费者使用shared_from_this()
来获取std::shared_ptr
,他们仍然需要互斥量(mutex)或其他同步原语来保证在任何时候只有一个线程能够修改队列的状态。
#include <deque>
#include <mutex>
#include <condition_variable>
#include <memory>
class ThreadSafeQueue : public std::enable_shared_from_this<ThreadSafeQueue> {
std::deque<int> data_queue;
std::mutex mutex;
std::condition_variable cond;
void push(int value) {
std::lock_guard<std::mutex> lock(mutex);
data_queue.push_back(value);
cond.notify_one();
}
int pop() {
std::unique_lock<std::mutex> lock(mutex);
cond.wait(lock, [this] { return !data_queue.empty(); });
int result = data_queue.front();
data_queue.pop_front();
return result;
}
};
5. enable_shared_from_this的实现原理
5.1 原理阐述
enable_shared_from_this
是一个模板类,允许对象通过 shared_from_this()
成员函数获得指向自身的 shared_ptr
。其实现原理与 weak_ptr
密切相关。
当一个对象继承了 enable_shared_from_this
,并被一个 shared_ptr
管理时,shared_ptr
会在内部维护一个 weak_ptr
指向该对象。这个 weak_ptr
是 enable_shared_from_this
的成员变量 weak_this_
。当调用 shared_from_this()
时,实际上是通过 weak_this_
创建一个新的 shared_ptr
,这样确保了 shared_ptr
的引用计数是正确的。
具体来说,当 shared_ptr
被构造时,它会检测对象是否继承自 enable_shared_from_this
,如果是,则初始化 weak_this_
指向该对象。这样,当 shared_from_this()
被调用时,weak_this_
就能生成一个新的 shared_ptr
,确保引用计数的正确性,避免重复删除对象。
5.2 原理伪代码示例
以下是 enable_shared_from_this
的实现原理的简单伪代码示例:
// 定义 enable_shared_from_this 模板类
template <typename T>
class enable_shared_from_this {
public:
std::shared_ptr<T> shared_from_this() {
return weak_this_.lock(); // 使用 weak_ptr 创建 shared_ptr
}
protected:
// 构造函数,默认初始化 weak_this_
enable_shared_from_this() {}
private:
std::weak_ptr<T> weak_this_; // 用于存储对象的 weak_ptr
// 允许 shared_ptr 访问私有成员
template <typename U>
friend class std::shared_ptr;
};
// 自定义 shared_ptr 的构造函数
template <typename T>
class shared_ptr {
public:
shared_ptr(T* ptr) : ptr_(ptr) {
// 检查对象是否继承自 enable_shared_from_this
if (auto enable_shared = dynamic_cast<enable_shared_from_this<T>*>(ptr)) {
enable_shared->weak_this_ = *this; // 初始化 weak_this_
}
}
// 其他 shared_ptr 成员函数...
private:
T* ptr_; // 实际管理的对象指针
};
// 示例类继承 enable_shared_from_this
class MyClass : public enable_shared_from_this<MyClass> {
public:
void func() {
std::shared_ptr<MyClass> self = shared_from_this(); // 获取指向自身的 shared_ptr
// 使用 self 进行操作...
}
};
// 使用示例
int main() {
std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
obj->func(); // 正确使用 shared_from_this()
return 0;
}
这个伪代码展示了 enable_shared_from_this
的基本工作原理:
enable_shared_from_this
内部维护一个weak_ptr
成员weak_this_
。- 当对象被
shared_ptr
管理时,shared_ptr
会初始化weak_this_
。 - 调用
shared_from_this()
时,通过weak_this_
创建一个新的shared_ptr
,确保引用计数正确。
5.3 原理示意图
以下是一个更直观的图来说明 enable_shared_from_this
的工作原理:
- 创建
shared_ptr
管理的对象
1. 创建 shared_ptr 管理对象
+--------------------+ +-------------------------+
| shared_ptr<MyClass>| | MyClass : enable_... |
|--------------------| |-------------------------|
| + ref_count = 1 | | + weak_this_ (未初始化) |
| + ptr ------------>|-----------> | + 其他成员 |
+--------------------+ +-------------------------+
shared_ptr<MyClass> obj = std::make_shared<MyClass>();
- 初始化
weak_this_
2. shared_ptr 构造函数初始化 weak_this_
+--------------------+ +-------------------------+
| shared_ptr<MyClass>| | MyClass : enable_... |
|--------------------| |-------------------------|
| + ref_count = 1 | | + weak_this_ ---------->|
| + ptr ------------>|-----------> | + 其他成员 |
|--------------------| +-------------------------+
- 调用
shared_from_this
3. 调用 shared_from_this() 获取 shared_ptr
+--------------------+ +-------------------------+
| shared_ptr<MyClass>| | MyClass : enable_... |
|--------------------| |-------------------------|
| + ref_count = 2 |<----------- | + weak_this_ (指向对象) |
| + ptr ------------>| | + 其他成员 |
+--------------------+ +-------------------------+
std::shared_ptr<MyClass> self = obj->shared_from_this();
- 图示说明
创建
shared_ptr
管理对象:- 当我们创建一个
shared_ptr
来管理一个继承自enable_shared_from_this
的对象时,shared_ptr
的构造函数会将对象指针传递给shared_ptr
,并初始化引用计数。
- 当我们创建一个
初始化
weak_this_
:- 如果对象继承自
enable_shared_from_this
,shared_ptr
的构造函数会将enable_shared_from_this
内部的weak_this_
初始化为指向该对象的weak_ptr
。
- 如果对象继承自
调用
shared_from_this
:- 当我们调用
shared_from_this
时,weak_this_
会返回一个新的shared_ptr
,这样保证了引用计数的正确管理,避免对象被重复删除。
- 当我们调用
6. 使用 enable_shared_from_this
常见错误
情形1:
class Test : public std::enable_shared_from_this<Test> {
public:
Test() {
std::shared_ptr<Test> pTest = shared_from_this();
}
};
这种用法是错误的。虽然对象的基类 enable_shared_from_this
的构造函数已经被调用,但 shared_ptr
的构造函数并没有被调用,因此 weak_ptr weak_this_
没有被初始化,所以调用 shared_from_this()
是错误的。
情形2:
class Test : public std::enable_shared_from_this<Test> {
public:
void func() {
std::shared_ptr<Test> pTest = shared_from_this();
}
};
int main() {
Test test;
test.func(); // 错误
std::shared_ptr<Test> pTest = std::make_shared<Test>();
pTest->func(); // 正确
return 0;
}
这种做法同样是错误的,原因与情形1相同。shared_ptr
的构造函数并没有被调用,因此 weak_ptr weak_this_
没有被初始化。
正确做法:
class Test : public std::enable_shared_from_this<Test> {
public:
void func() {
std::shared_ptr<Test> pTest = shared_from_this();
}
};
int main() {
std::shared_ptr<Test> pTest = std::make_shared<Test>();
pTest->func();
return 0;
}
std::shared_ptr<Test> pTest = std::make_shared<Test>();
这句话依次执行的顺序是:
- 调用
enable_shared_from_this
的构造函数。 - 调用
Test
的构造函数。 - 调用
shared_ptr
的构造函数初始化weak_ptr weak_this_
。
最后才能通过 func()
函数使用 shared_from_this()
。
其他注意事项:
- 不能在对象的构造函数中使用
shared_from_this()
。 - 需要依次调用
enable_shared_from_this
的构造函数、对象的构造函数,最后调用shared_ptr
的构造函数初始化enable_shared_from_this
的成员变量weak_this_
,然后才能使用shared_from_this()
。 - 如果程序中使用了智能指针
shared_ptr
,则整个程序应统一使用智能指针,不能使用原始指针,以免出现错误。
7. 总结
使用 std::enable_shared_from_this
是在多线程环境中安全使用 std::shared_ptr
和 std::weak_ptr
的一种辅助方法。它有效地避免了因对象提前销毁而导致的悬空指针访问问题,提升了程序的稳定性和可靠性。然而,在设计和实现多线程应用时,仍需综合考虑并正确使用同步机制,才能彻底避免竞态条件和其他多线程安全性问题的发生。
8. 参考文章
enable_shared_from_this模板类使用完全解析
C++ enable_shared_from_this解析