weak_ptr与shared_ptr

c++智能指针介绍

由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete,比如流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见,并造成内存泄露。如此c++引入 智能指针 ,智能指针即是C++ RAII的一种应用,可用于动态资源管理,资源即对象的管理策略。 智能指针在 <memory> 标头文件的 std 命名空间中定义。 它们对RAII 或 获取资源即初始化 编程惯用法至关重要。 RAII 的主要原则是为所有堆分配资源提供所有权,例如动态分配内存或系统对象句柄、析构函数包含要删除或释放资源的代码的堆栈分配对象,以及任何相关清理代码。

c++智能指针类别

c++ 智能指针主要包括:unique_ptr,shared_ptr, weak_ptr, 这三种,其中auto_ptr 已被遗弃。 

unique_ptr 
只允许基础指针的一个所有者。 可以移到新所有者(具有移动语义),但不会复制或共享(即我们无法得到指向同一个对象的两个unique_ptr)。 替换已弃用的 auto_ptr。 相较于 boost::scoped_ptr。 unique_ptr 小巧高效;大小等同于一个指针,支持 rvalue 引用,从而可实现快速插入和对 STL 集合的检索。 头文件:<memory>。

使用unique_ptr,可以实现以下功能:

1、为动态申请的内存提供异常安全。 
2、将动态申请内存的所有权传递给某个函数。 
3、从某个函数返回动态申请内存的所有权。

4、在容器中保存指针。 
5、所有auto_ptr应该具有的(但无法在C++ 03中实现的)功能。 

如下代码所示:

class A;
// 如果程序执行过程中抛出了异常,unique_ptr就会释放它所指向的对象
// 传统的new 则不行
unique_ptr<A> fun1()
{
	unique_ptr p(new A);
	//do something
	return p;
}

void fun2()
{   //  unique_ptr具有移动语义
	unique_ptr<A> p = f();// 使用移动构造函数
	// do something
}// 在函数退出的时候,p以及它所指向的对象都被删除释放
 shared_ptr 
采用引用计数的智能指针。 shared_ptr基于“引用计数”模型实现,多个shared_ptr可指向同一个动态对象,并维护了一个共享的引用计数器,记录了引用同一对象的shared_ptr实例的数量。当最后一个指向动态对象的shared_ptr销毁时,会自动销毁其所指对象(通过delete操作符)。shared_ptr的默认能力是管理动态内存,但支持自定义的Deleter以实现个性化的资源释放动作。头文件:<memory>。 

基本操作:shared_ptr的创建、拷贝、绑定对象的变更(reset)、shared_ptr的销毁(手动赋值为nullptr或离开作用域)、指定deleter等操作。

 shared_ptr的创建,有两种方式,一,使用函数make_shared(会根据传递的参数调用动态对象的构造函数);二,使用构造函数(可从原生指针、unique_ptr、另一个shared_ptr创建) 

shared_ptr<int> p1 = make_shared<int>(1);// 通过make_shared函数
    shared_ptr<int> p2(new int(2));// 通过原生指针构造
此外智能指针若为“空“,即不指向任何对象,则为false,否则为true,可作为条件判断。可以通过两种方式指定deleter,一是构造shared_ptr时,二是使用reset方法时。可以重载的operator->, operator *,以及其他辅助操作如unique()、use_count(), get()等成员方法。 

 weak_ptr 
结合 shared_ptr 使用的特例智能指针。 weak_ptr 提供对一个或多个 shared_ptr 实例所属对象的访问,但是,不参与引用计数。 如果您想要观察对象但不需要其保持活动状态,请使用该实例。 在某些情况下需要断开 shared_ptr 实例间的循环引用。 头文件:<memory>。 

weak_ptr的用法如下:

weak_ptr用于配合shared_ptr使用,并不影响动态对象的生命周期,即其存在与否并不影响对象的引用计数器。weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象。提供了expired()与lock()成员函数,前者用于判断weak_ptr指向的对象是否已被销毁,后者返回其所指对象的shared_ptr智能指针(对象销毁时返回”空“shared_ptr)。循环引用的场景:如二叉树中父节点与子节点的循环引用,容器与元素之间的循环引用等。 

智能指针的循环引用

循环引用问题可以参考 这个链接 上的问题理解,“循环引用”简单来说就是:两个对象互相使用一个shared_ptr成员变量指向对方的会造成循环引用。导致引用计数失效。下面给段代码来说明循环引用:

#include <iostream>
#include <memory>
using namespace std;

class B;
class A
{
public:// 为了省去一些步骤这里 数据成员也声明为public
	//weak_ptr<B> pb;
	shared_ptr<B> pb;
	void doSomthing()
	{
//		if(pb.lock())
//		{
//
//		}
	}

	~A()
	{
		cout << "kill A\n";
	}
};

class B
{
public:
	//weak_ptr<A> pa;
	shared_ptr<A> pa;
	~B()
	{
		cout <<"kill B\n";
	}
};

int main(int argc, char** argv)
{
	shared_ptr<A> sa(new A());
	shared_ptr<B> sb(new B());
	if(sa && sb)
	{
		sa->pb=sb;
		sb->pa=sa;
	}
	cout<<"sa use count:"<<sa.use_count()<<endl;
	return 0;
}
上面的代码运行结果为:sa use count:2, 注意此时sa,sb都没有释放,产生了内存泄露问题!!!

即A内部有指向B,B内部有指向A,这样对于A,B必定是在A析构后B才析构,对于B,A必定是在B析构后才析构A,这就是循环引用问题,违反常规,导致内存泄露。

一般来讲,解除这种循环引用有下面有三种可行的方法( 参考 ): 
1 . 当只剩下最后一个引用的时候需要手动打破循环引用释放对象。 
2 . 当A的生存期超过B的生存期的时候,B改为使用一个普通指针指向A。 
3 . 使用弱引用的智能指针打破这种循环引用。 
虽然这三种方法都可行,但方法1和方法2都需要程序员手动控制,麻烦且容易出错。我们一般使用第三种方法:弱引用的智能指针weak_ptr。 

强引用和弱引用 
一个强引用当被引用的对象活着的话,这个引用也存在(就是说,当至少有一个强引用,那么这个对象就不能被释放)。share_ptr就是强引用。相对而言,弱引用当引用的对象活着的时候不一定存在。仅仅是当它存在的时候的一个引用。弱引用并不修改该对象的引用计数,这意味这弱引用它并不对对象的内存进行管理,在功能上类似于普通指针,然而一个比较大的区别是,弱引用能检测到所管理的对象是否已经被释放,从而避免访问非法内存。 

使用weak_ptr来打破循环引用 

代码如下:

#include <iostream>
#include <memory>
using namespace std;

class B;
class A
{
public:// 为了省去一些步骤这里 数据成员也声明为public
	weak_ptr<B> pb;
	//shared_ptr<B> pb;
	void doSomthing()
	{
		if(pb.lock())
		{

		}
	}

	~A()
	{
		cout << "kill A\n";
	}
};

class B
{
public:
	//weak_ptr<A> pa;
	shared_ptr<A> pa;
	~B()
	{
		cout <<"kill B\n";
	}
};

int main(int argc, char** argv)
{
	shared_ptr<A> sa(new A());
	shared_ptr<B> sb(new B());
	if(sa && sb)
	{
		sa->pb=sb;
		sb->pa=sa;
	}
	cout<<"sb use count:"<<sb.use_count()<<endl;
	return 0;
}

补充1:为什么不能检测shared_ptr是否为NULL?

因为这就是错的。shared_ptrnullptr相等,并不代表所引用的对象不存在了。

shared_ptr允许有一个“empty”状态,代表shared_ptr<T>对象的实例本身存在,但并不指向一个有效的Tshared_ptr重载了===来表示以下意义:

  • shared_ptrnullptr判等,代表判断是否处在empty状态;

  • shared_ptr被赋值为nullptr,不代表shared_ptr实例本身没了,而是把这个shared_ptr实例的状态改为empty。

  • 一个已经处于empty状态的shared_ptr仍可以被继续赋值成为一个有效、有值的shared_ptr

如果有两个shared_ptr同时指向同一个T在内存中的实例,那么任意一个shared_ptrnullptr判等成功,都不能说明指向的对象已经不存在(被销毁)了!

任何一个具体用例中,都必然有多个shared_ptr指向同一个实例(如果没有多个你shared干嘛)。所以通过判空某 1 个shared_ptr去确定被指向的对象是否已被销毁,这个在语义和实效上都绝无任何正确(哪怕是“蒙对了”)的可能!


补充2:shared_ptr到底是什么?

首先shared_ptr虽然名为“智能指针”,但其实他不是指针。一个shared_ptr<T>定义出来的东西,只是一个单纯的变量,存储了一个实例化出来的class。其本质上等同于以下代码:

struct MyClass
{
public:
    int MyValue;
    MyClass(int my_value) {
        MyValue = my_value;
    }
}

void main()
{
    auto a = new MyClass(1); 
    // a 是一个单纯的变量,不是指针
    // a 天然与 main() 函数拥有等同的生命周期
}

为什么shared_ptr能够像指针一样使用*&->等运算符,是因为shared_ptr类把这些运算符重载了,从而让程序编写者“看起来”像是在用指针而已。

一个非指针的局部变量,如果不靠运算符重载,你是不能判nullptr的。如同你不能对一个intnullptr一样。

shared_ptrnullptr由于运算符重载,其本质意义都变了,必须具体分析,而不能想当然!


嗯……非读者注意:StrBlob仅仅是那本书里的一个范例类,其内部用shared_ptr维护其管理的vector<string>集合本身的生存期。与C++本身无关。

阻止用户访问一个不再存在的vector,这个用shared_ptr没问题,能做到。事实上很多情况下,weak_ptr全改shared_ptr并不会立刻就炸。

但是shared_ptr就意味着你的引用和原对象是一个强联系。你的引用不解开,原对象就不能销毁。滥用强联系,这在一个运行时间长、规模比较大,或者是资源较为紧缺的系统中,极易造成隐性的内存泄漏,这会成为一个灾难性的问题。

更糟的是,滥用强联系可能造成循环引用的灾难。即:B持有指向A内成员的一个shared_ptrA也持有指向B内成员的一个shared_ptr,此时AB的生命周期互相由对方决定,事实上都无法从内存中销毁。 
——这还仅仅是一个简单的情况。如果存在间接的强引用,或者是多于两个实例之间的强引用,这个相关的bug解起来将是灾难性的。

shared_ptr是C++内存管理机制的一种放松。但放松绝不意味着可以滥用,否则最后的结局恐怕不会比裸指针到处申请了不释放更好。

必须明确:从语义上来说,shared_ptr代表了一种对生命周期的自动推断。其本质的意义是:A持有Bshared_ptr,代表B的生命周期反而完全覆盖了A。以树形结构的层级来理解,指针持有者是下级,指针指向的目标反而才是上级——下级短命,上级长存;上级不存,下级焉附。

从这个意义上,有些关系你用shared_ptr就表示不了了:

  • 隶属关系中,祖先到子孙的关系。比如一个对象容器,具体对象找其隶属的容器可以用shared_ptr,但是容器去找具体的对象则不行,因为容器不能要求对象生存的比自己更久。

  • 生命周期没有本质关联的两个无关对象。例如一个全局事件管理器(订阅者模型),事件订阅者构造时把自己注册进来,析构时把自己解注册掉。管理器要维护到达这个对象的指针(从而发送消息),但绝对不允许染指对象的生命周期,对象的析构需要无视订阅者的存在,只由其他业务所必须的强引用来控制。

这种时候就是weak_ptr的用处。weak_ptr提供一个(1)能够确定对方生存与否(2)互相之间生命周期无干扰(3)可以临时借用一个强引用(在你需要引用对方的短时间内保证对方存活)的智能指针。

weak_ptr要求程序员在运行时确定生存并加锁,这也是逻辑上必须的本征复杂度——如果别人活的比你短,你当然要:(1)先确定别人的死活(2)如果还活着,就给他续个命续到你用完了为止。


事实上弱引用强引用这个概念,在有GC的语言中也是一个需要注意的问题。以C#为例:

public class EventDisposer
{
    private Dictionary<int, WeakReference<IEventListener>> pendingEvents;
    private void Dispose(int event_id)
    {
        var reference = pendingEvents[event_id];
        IEventListener context;
        if (reference.TryGetTarget(out context)) 
        {
            // context is valid from here to the end of function
            context.Trigger();
        }
        else
        {
            // context has already been destroyed...
        }
    }
}

这个程序使用WeakReference<IEventListener>替代强引用的IEventListener,从而使接受事件分发的对象,在生存周期上可以获得彻底销毁的自由,而不和事件分发者产生什么必然的关联。

===================================================================================================================

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段. 
  weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少. 
  定义在 memory 文件中(非memory.h), 命名空间为 std.

  weak_ptr 使用:

std::shared_ptr<int> sp(new int(10));
std::weak_ptr<int> wp(sp);
wp = sp;
printf("%d\n", wp.use_count()); // 1
wp.reset();
printf("%d\n", wp); // 0

// 检查 weak_ptr 内部对象的合法性.
if (std::shared_ptr<int> sp = wp.lock())
{
}

 

成员函数

weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象. 注意, weak_ptr 在使用前需要检查合法性.

expired 用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.
lock 用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.
use_count 返回与 shared_ptr 共享的对象的引用计数.
reset 将 weak_ptr 置空.
weak_ptr 支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.

 

使用 weak_ptr 解决 shared_ptr 因循环引有不能释放资源的问题

使用 shared_ptr 时, shared_ptr 为强引用, 如果存在循环引用, 将导致内存泄露. 而 weak_ptr 为弱引用, 可以避免此问题, 其原理:
  对于弱引用来说, 当引用的对象活着的时候弱引用不一定存在. 仅仅是当它存在的时候的一个引用, 弱引用并不修改该对象的引用计数, 这意味这弱引用它并不对对象的内存进行管理.
  weak_ptr 在功能上类似于普通指针, 然而一个比较大的区别是, 弱引用能检测到所管理的对象是否已经被释放, 从而避免访问非法内存。
注意: 虽然通过弱引用指针可以有效的解除循环引用, 但这种方式必须在程序员能预见会出现循环引用的情况下才能使用, 也可以是说这个仅仅是一种编译期的解决方案, 如果程序在运行过程中出现了循环引用, 还是会造成内存泄漏.

复制代码
         class CB;
        class CA;
     
        class CA
        {
        public:
            CA(){}
            ~CA(){PRINT_FUN();}
     
            void Register(const std::shared_ptr<CB>& sp)
            {
                m_spb = sp;
            }
     
        private:
            std::weak_ptr<CB> m_spb;
        };
     
        class CB
        {
        public:
            CB(){};
            ~CB(){PRINT_FUN();};
     
            void Register(const std::shared_ptr<CA>& sp)
            {
                m_spa = sp;
            }
     
        private:
            std::shared_ptr<CA> m_spa;
        };
     
        std::shared_ptr<CA> spa(new CA);
        std::shared_ptr<CB> spb(new CB);
     
        spb->Register(spa);
        spa->Register(spb);
        printf("%d\n", spb.use_count()); // 1
        printf("%d\n", spa.use_count()); // 2
复制代码

 

 

 

VC中的源码实现

复制代码
template<class _Ty>
class weak_ptr
    : public _Ptr_base<_Ty>
{    // class for pointer to reference counted resource
    typedef typename _Ptr_base<_Ty>::_Elem _Elem;

public:
    weak_ptr()
    {    // construct empty weak_ptr object
    }

    template<class _Ty2>
    weak_ptr(const shared_ptr<_Ty2>& _Other,
        typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
        void *>::type * = 0)
    {    // construct weak_ptr object for resource owned by _Other
        this->_Resetw(_Other);
    }

    weak_ptr(const weak_ptr& _Other)
    {    // construct weak_ptr object for resource pointed to by _Other
        this->_Resetw(_Other);
    }

    template<class _Ty2>
    weak_ptr(const weak_ptr<_Ty2>& _Other,
        typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
        void *>::type * = 0)
    {    // construct weak_ptr object for resource pointed to by _Other
        this->_Resetw(_Other);
    }

    ~weak_ptr()
    {    // release resource
        this->_Decwref();
    }

    weak_ptr& operator=(const weak_ptr& _Right)
    {    // assign from _Right
        this->_Resetw(_Right);
        return (*this);
    }

    template<class _Ty2>
    weak_ptr& operator=(const weak_ptr<_Ty2>& _Right)
    {    // assign from _Right
        this->_Resetw(_Right);
        return (*this);
    }

    template<class _Ty2>
    weak_ptr& operator=(shared_ptr<_Ty2>& _Right)
    {    // assign from _Right
        this->_Resetw(_Right);
        return (*this);
    }

    void reset()
    {    // release resource, convert to null weak_ptr object
        this->_Resetw();
    }

    void swap(weak_ptr& _Other)
    {    // swap pointers
        this->_Swap(_Other);
    }

    bool expired() const
    {    // return true if resource no longer exists
        return (this->_Expired());
    }

    shared_ptr<_Ty> lock() const
    {    // convert to shared_ptr
        return (shared_ptr<_Elem>(*this, false));
    }
};
复制代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值