Item19 Use std::shared_ptr for shared-ownership resource management

​   在上一篇文章中讨论了std::unique_ptr,本文讨论的则是另外一个比较重要的智能指针std::shared_ptr,通过本文标题可知,这是一个共享所有权的智能指针,使用这个智能指针可以实现弱化的垃圾回收(Python的垃圾回收就是基于引用计数机制),C++一直被那些带有gc的编程语言所鄙视,而我觉得C++的智能指针是要比gc更先进的一种机制,因为gc只能帮你管理内存资源,但是资源并不仅仅是内存,还有其它诸如文件fdsocket描述符等,C++通过RAII,和引用计数型智能指针管理所有的资源。

​   从上文中我们已经讨论了shared_ptr的基本概念,接下来我们来谈论一下相关的细节。shared_ptr是一个值语义的模版类,可以任意的复制,拷贝和传递,并自动更新引用计数值,从而控制资源的生命周期。因此,shared_ptr需要保存原始指针和引用计数等信息,所以它的大小是普通指针的两倍,引用计数是使用的原子变量保存的其目的是为了线程安全,而且必须是放在堆内存中,这样在拷贝的时候就可以共享引用计数值了。为了减少不必要的性能损耗在拷贝shared_ptr的时候应该尽可能的使用std::move进行移动,而不是拷贝构造。

​   接下来我们谈论下定制删除器的问题,在上一篇文章中说到std::unique_ptr可以自定义删除器,不过会导致智能指针的大小变大,而这个问题在shared_ptr身上是不存在的,究其原因是因为对于shared_ptr来说,自定义删除器的信息和引用计数的信息是放在一起的称之为控制块,在shared_ptr中有一个指针指向这个控制块,这个指针的大小是不会因为自定义删除器而改变的,如下图: 这里写图片描述

​ 最后谈论的是enable_shared_from_this这个模版类,在讲这个话题之前,先来看一个例子:

std::vector<std::shared_ptr<Widget> processedWidget;
class Widget {
  public:
    ...
    void process();
    ...
};
void Widget::process() {
  processedWidget.emplace_back(this);   //这里存在问题
}

​   上面这段代码是有潜在的问题的,究其原因在于上文中提到的控制块,对于一个资源来说只会创建一个控制块,然后所有的shared_ptr都指向这个控制块,如果创建了两个就会导致未定义的行为,例如下面这段代码:

auto pw = new Widget;
std::shared_ptr<Widget> spw1(pw);
std::shared_ptr<Widget> spw2(pw);

​​   上面的代码spw1spw2分别针对资源pw创建了一个控制块,这在析构的时候会导致资源pw被释放两次,同理,在上面的代码中processedWidget.emplace_back(this)会导致创建一个shared_ptr指向this指针,并创建一个控制块管理这块资源。如果外部也有一个shared_ptr指向this指针,并创建了一个控制块,就会导致未定义的行为(重复释放两次资源),例如下面的代码:

class Widget;
std::vector<std::shared_ptr<Widget>> processedWidget;
auto loggingDel = [](Widget *pw) {
  delete pw; 
};  
class Widget {
  public:
    void process();
};
void Widget::process()  {
  processedWidget.emplace_back(this, loggingDel); //这里存在问题
}
int main() {
  {
    std::shared_ptr<Widget> w(new Widget, loggingDel);
    w->process();
  }
  return 0;
}

​   上面的代码会出现未定义的行为,为了解决这个问题,所以就引入了enable_shared_from_this这个话题,先来看下它的用法:

class Widget;
std::vector<std::shared_ptr<Widget>> processedWidget;
auto loggingDel = [](Widget *pw) {
  delete pw;
};
class Widget : public std::enable_shared_from_this<Widget> {
  public:
    void process();
};
void Widget::process()  {
  processedWidget.emplace_back(shared_from_this());
}
int main() {
  {
    std::shared_ptr<Widget> w(new Widget, loggingDel);
    w->process();
  }
  return 0;
}

​   上面的代码就不存在double free的问题了,在使用shared_from_this返回this指针的shared_ptr的时候会先搜索当前对象的控制块,如果有就不会再创建控制块了。所以也就没有上面提到的问题,如果没有则会出现未定义行为,会抛出异常。

好的,以下是关于直接将`this`指针作为`std::shared_ptr`返回的问题及解决方案的精简总结: --- 直接将`this`指针作为`std::shared_ptr`返回的问题 1.引用计数问题 `std::shared_ptr`使用引用计数来管理对象的生命周期。当一个对象被多个`std::shared_ptr`实例共享时,它们通过共享同一个控制块来维护引用计数。如果直接将`this`指针传递给`std::shared_ptr`,会创建一个新的控制块,导致多个独立的`std::shared_ptr`实例指向同一个对象,但它们的引用计数是独立的。这会导致以下问题: • 重复析构:当其中一个`std::shared_ptr`的生命周期结束时,它会减少引用计数并释放对象。如果其他`std::shared_ptr`仍然存在,它们可能会尝试访问或释放已经被释放的对象,从而导致未定义行为。 • 生命周期管理混乱:多个独立的`std::shared_ptr`无法正确同步对象的生命周期,可能导致对象过早释放或内存泄漏。 2.内存管理问题 直接返回`this`指针作为`std::shared_ptr`绕过了`std::shared_ptr`的内存管理机制,可能会导致以下问题: • 内存泄漏:如果对象在返回`std::shared_ptr`之前已经被释放,那么后续使用该`std::shared_ptr`时可能会导致错误。 • 未定义行为:访问已释放的内存可能导致程序崩溃或其他不可预测的行为。 3.线程安全问题 在多线程环境中,直接返回`this`指针作为`std::shared_ptr`可能导致线程安全问题: • 数据竞争:多个线程可能同时访问和修改同一个对象,而没有适当的同步机制,这可能会导致数据竞争和其他并发问题。 • 引用计数混乱:如果多个线程同时创建独立的`std::shared_ptr`,它们的引用计数可能会出现不一致的情况。 --- 解决方案:`std::enable_shared_from_this` 为了安全地返回`this`指针作为`std::shared_ptr`,应该让目标类继承`std::enable_shared_from_this`,并使用其成员函数`shared_from_this()`来返回`this`的`std::shared_ptr`。`std::enable_shared_from_this`的内部实现机制如下: • 内部维护一个`std::weak_ptr`:`std::enable_shared_from_this`类内部维护一个`std::weak_ptr`,用于观察当前对象的生命周期。 • `shared_from_this()`的实现:当调用`shared_from_this()`时,它会通过内部的`std::weak_ptr`调用`lock()`方法来获取一个共享当前对象生命周期的`std::shared_ptr`。这样可以确保所有`std::shared_ptr`实例共享同一个控制块,从而避免引用计数问题。 示例代码 ```cpp #include <memory> #include <iostream> class MyClass : public std::enable_shared_from_this<MyClass> { public: std::shared_ptr<MyClass> getShared() { return shared_from_this(); // 安全地返回 this 的 std::shared_ptr } void doSomething() { std::cout << "Doing something..." << std::endl; } }; int main() { std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>(); std::shared_ptr<MyClass> anotherPtr = ptr->getShared(); // 获取共享的 std::shared_ptr anotherPtr->doSomething(); return 0; } ``` 注意事项 1. `shared_from_this()`的使用时机: • `shared_from_this()`只能在对象已经被`std::shared_ptr`管理后使用。如果在对象尚未被`std::shared_ptr`管理时调用`shared_from_this()`,会抛出异常。 • 例如,不能在构造函数中调用`shared_from_this()`,因为此时对象尚未被`std::shared_ptr`管理。 2. 线程安全: • `std::shared_ptr`的引用计数操作是线程安全的,但`std::enable_shared_from_this`的内部`std::weak_ptr`也需要正确同步。在多线程环境中,确保对象的生命周期管理是线程安全的。 --- 通过使用`std::enable_shared_from_this`,可以安全地返回`this`指针作为`std::shared_ptr`,同时避免引用计数问题、内存管理问题和线程安全问题。
最新发布
05-06
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值