######################################## 智能指针 ######################################## .. seealso:: - `【C++】智能指针详解 `_ 什么是智能指针? - 智能指针是一个句柄 - 智能指针在离开作用域时自动调用实际指针的析构函数 第一点句柄代表了智能指针本身不会失效,因此没有判空问题 第二点代表了智能指针可以自动管理内存 shared_ptr 和 make_shared **************************************** 如下代码: .. code-block:: cpp shared_ptr str(nullptr); auto str2 = make_shared(); *str2 = "haha"; assert(str == nullptr); assert(*str2 == "haha"); 使用 shared_ptr 创建出来的对象用于绑定一个已有的堆对象,其参数为一个对象指针,因此 **可以绑定到空指针** ,但是 make_shared 是直接创建了一个匿名的堆对象,并直接使用 share_ptr 管理它,其参数是参数 T 的构造参数,因此不能绑定到空指针。 share_ptr 的注意事项 **************************************** 根据 `C++ shared_ptr四宗罪) `_ 可以由以下结论: - 一个对象只能由一个 shared_ptr 管理,否则会出现多次析构。因此 shared_ptr 适用于移动语义 - shared_ptr 应当至少与原对象的声明周期相同,但是一定不能比原对象的声明周期短。 - shared_ptr 无法转移对象的所有权 - 一旦使用 shared_ptr,则 shared_ptr 将会从库中传播到用户代码中,则唯一的方法就是使用回调函数。 - 若资源在外部已经由 shared_ptr 管理了,这时候资源需要获取指向自己的 shared_ptr,那么唯一的办法就是获取一个引用计数与外部 shared_ptr 共享的智能指针。这唯一的方法就是让对象继承 enable_shared_from_this - 智能指针在多线程中性能不佳。 enable_shared_from_this **************************************** 以下为摘录内容 我们来看看具体的代码实现逻辑: .. code-block:: cpp struct Good: std::enable_shared_from_this // 注意:继承 { std::shared_ptr getptr() { return shared_from_this(); } }; struct Bad { // 错误写法:用不安全的表达式试图获得 this 的 shared_ptr 对象 std::shared_ptr getptr() { return std::shared_ptr(this); } }; 这里我们可以看到,Good类继承了std::enable_shared_from_this,并且自己是作为模板参数传递给父类的。这就给让代码看起来有些"唬人",看起来像是继承自己一样。但其实呢?这里只是用到了模板派生,让父类能够在编译器感知到子类的模板存在,二者不是真正意义上的继承关系。 这里只分析下面两个问题: #. 为什么Bad类直接通过this构造shared_ptr会存在问题? 答:因为原本的this指针就是被shared_ptr管理的,通过getprt函数构造的新的智能指针和和原本管理this指针的的shared_ptr并不互相感知。这会导致指向Bad的this指针被二次释放!!! #. 为什么通过继承std::enable_shared_from_this之后就没有上述问题了? 答:这里截取了部分std::enable_shared_from_this的源码并且简化了一下: .. code-block:: cpp template class enable_shared_from_this { protected: enable_shared_from_this(const enable_shared_from_this&) noexcept { } ~enable_shared_from_this() { } public: shared_ptr<_Tp> shared_from_this() { return shared_ptr<_Tp>(this->_M_weak_this); } shared_ptr shared_from_this() const { return shared_ptr(this->_M_weak_this); } private: mutable weak_ptr<_Tp> _M_weak_this; }; std::enable_shared_from_this 的实现由于有些复杂,受限于篇幅。笔者就不展开来分析它具体是怎么样实现的了。它的能够规避上述问题的原因如下: 通过自身维护了一个 ``std::weak_ptr`` 让所有从该对象派生的 ``shared_ptr`` 都通过了 ``std::weak_ptr`` 构造派生。 ``std::shared_ptr`` 的构造函数判断出对象是 ``std::enable_shared_from_this`` 的之类之后也会同样通过对象本身的 ``std::weak_ptr`` 构造派生。这个这样引用计数是互通的,也就不会存在上述 double delete 的问题了。 .. seealso:: - `智能指针的线程安全 `_ weak_ptr **************************************** weak_ptr 用于关联到一个 shared_ptr 上。而且这个关联不会导致 shared_ptr 中的引用计数增加。主要目的是防止环形引用 weak_ptr 比较重要的一点是用来做资源的 handle,这样就不用担心空指针的问题了,因为 handle 是不会失效的 另外 析构动作在创建时被捕获 这是一个非常有用的特性,这意味着: - 虚析构不再是必需的 - shared_ptr 可以持有任何对象,而且能安全地释放 - shared_ptr 对象可以安全地跨越模块边界,比如从 DLL 里返回,而不会造成从模块 A 分配的内存在模块 B 里被释放这种错误 - 二进制兼容性,即便 Foo 对象的大小变了,那么旧的客户代码仍然可以使用新的动态库,而无须重新编译。前提是 Foo 的头文件中不出现访问对象的成员的 inline 函数,并且 Foo 对象的由动态库中的 Factory 构造,返回其 shared_ptr - 析构动作可以定制 析构所在的线程 对象的析构是同步的,当最后一个指向 x 的 shared_ptr 离开其作用域的时候,x 会同时在同一个线程析构。这个线程不一定是对象诞生的线程。 这个特性是把双刃剑:如果对象的析构比较耗时,那么可能会拖慢关键线程的速度(如果最后一个 shared_ptr 引发的析构发生在关键线程);同时,我们可以用一个单独的线程来专门做析构,通过一个 BlockingQueue > 把对象的析构都转移到那个专用线程,从而解放关键线程。 .. code-block:: cpp std::shared_ptr getptr() { return shared_ptr(this); } 没问题,但是 .. code-block:: cpp this->shared_from_this() 会出现问题 对象池 **************************************** 对象池算是对智能指针指针的一次综合运用 .. code-block:: cpp class Stock { string key_; public: Stock(string key) : key_(key) { } const string& key() { return key_; } }; class StockFactory : enable_shared_from_this { mutable mutex mutex_; map> stocks_; public: shared_ptr get(const string& key) { std::lock_guard lock(mutex_); auto& wkStock = stocks_[key]; auto pStock = wkStock.lock(); if(!pStock) { pStock.reset(new Stock(key), bind(&StockFactory::weakDeleteCallBack, weak_ptr(this->shared_from_this()), std::placeholders::_1)); } return pStock; } static void weakDeleteCallBack(const weak_ptr& wkFactory, Stock* stock) { auto factory = wkFactory.lock(); if(factory) factory->removeStock(stock); delete stock; } void removeStock(Stock* stock) { if(stock) { lock_guard lock(mutex_); stocks_.erase(stock->key()); } } };