智能指针的内部实现、循环引用(weak_ptr解决)

转载自:https://blog.csdn.net/i_chaoren/article/details/82586456
https://www.jianshu.com/p/11389ccbcf79
https://blog.csdn.net/daniel_ustc/article/details/23096229

智能指针的内部实现

  1. 智能指针最终的实现是 两个指针成员:一个指向数据成员一个指向计数器成员
  2. 智能指针里的计数器 维护的是一个指针,指向的 实际内存 在堆上,不是栈上的

智能指针拷贝构造的原理

shared_ptr智能指针内存管理思路

智能指针的一种通用实现技术是使用引用计数。智能指针类将一个计数器与智能指针指向的对象相关联,用来记录有多少个智能指针指向相同的对象,并在恰当的时候释放对象。

每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,引用计数加1;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

下图是shared_ptr的内存模型:

由图中可以看到,实际上引用计数、自定义销毁等都不是直接存储在shared_ptr中,而是通过一个指针指向的一个控制块存储的,控制块是动态分配的内存(引用计数和指向对象都在堆上、指针在栈上)。

reset

reset的功能是:释放对象,默认情况下置空指针。能够被安全地多次调用。

p.reset()
p.reset(q)
p.reset(q, d)

如果p是唯一指向其对象的shard_ptr, reset会释放对象(unique_ptr则会直接释放)。

如果传递了可选参数内指针q,则会令p指向p;
否则将p置空。

如果还传递了参数d,则将会调用d而不是delete来释放q。释放q的时机不是一定发生在reset时,而是q在运行到需要释放的时候。这里说明的是 reset会传递给它一个单独的delete函数

智能指针赋值nullptr

表现和reset的行为一致,会递减对象的计数器,如果减为0,则调用析构,释放对象。

class C 
{
public:
    ~C() 
    {
        cout << "C dtor" << endl;
    }
};

using namespace cycle_ref;
shared_ptr<cycle_ref::C> sp(new cycle_ref::C());
sp = nullptr;
cout << "before exit" << endl;

运行结果:在赋值nullptr时,调用了析构。

C dtor
before exit

unique_ptr

一个unique_ptr拥有它所指向的对象。

  1. 与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。
  2. 当unique_ptr被销毁时,它所指向的对象也被销毁。

uniqueptr不支持普通的拷贝和赋值操作,但是可以通过release or reset来转移所有权。

用途

管理一些对象:不需要被共享,但也希望能够在超出作用域时,自动释放资源。

void test_uniq_ptr() 
{
    std::unique_ptr<int> p1(new int(3));
//    std::unique_ptr<int> b;
//    b = a; //不支持赋值操作
//    std::unique_ptr<int> b = a;  //不支持拷贝构造
    
    //把p1指向的对象转移给p2
    std::unique_ptr<int> p2(p1.release());// release操作会自动给p1赋空
    cout << *p2 << endl;
    if (p1 == nullptr) 
        cout << "p1 is nullptr" << endl;
    
    std::unique_ptr<int> p3(new int(4));
    
    //reset 会释放本对象
    //release 会转移所有权
    p2.reset(p3.release()); //p2释放原来的对象,同时指向p3所指向的对象,p3赋空
    if (p3 == nullptr)
        cout << "p3 is nullptr" << endl;
    
    //释放对象,同时赋空
    p2.reset();
    if (p2 == nullptr) 
        cout << "p2 is nullptr" << endl;
    
    //release操作会放弃当前指针的控制权,但是并不会销毁它。
    //因此通常调用release操作
    // 是为了 赋值  给另一个智能指针,
    // 而不是 为了销毁对象
    std::unique_ptr<int> p(new int(3));
    auto px = p.release(); //让出指针所有权,但是并没有销毁
    delete px; //这里需要手动delete px
}

weak_ptr

是一种不控制所指向对象生命周期的智能指针,它指向一个shared_ptr管理的对象。

weak_ptr绑定到shared_ptr时,不会改变对象的引用计数。
当shared_ptr被销毁时,指向的对象也被销毁。不论weak_ptr是否指向了它。

用途

在不影响 智能指针所指向对象的生存周期的同时,判断对象是否存在(使用 lock方法),从而避免 访问一个不存在的对象 的情况。

void test_shared_ptr() 
{
    std::shared_ptr<int> sp = std::make_shared<int>(3);
    cout << *sp << endl;
    
    std::weak_ptr<int> wp(sp);
    cout << wp.use_count() << endl;
    
    //expired含义
    //判断use_count是否为0? 如果为0,返回true,否则返回false
    if (wp.expired()) 
        cout << "obj is deleted" << endl;
    else 
        cout << "obj exist" << endl;
    
    sp.reset();
    
    //lock的含义:
    //判断wp指向的对象是否存在?
    //如果存在,则返回非空(指向w的对象的智能指针);否则返回空(智能指针)
    auto ret = wp.lock();
    if (ret) 
        cout << "obj exist" << endl;
    else
        cout << "obj not exist" << endl;
    
    sp.reset();
    if (sp == nullptr) 
        cout << "reset will assign nullptr" << endl;
    sp.reset();
    sp.reset();
    cout << "it is safe to call reset any times" << endl;
}

输出结果:

3
1
obj exist
obj not exist
reset will assign nullptr
it is safe to call reset any times

循环引用计数问题

“循环引用”简单来说就是:两个对象互相使用一个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。

使用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()
    {
        shared_ptr<B> pp = pb.lock();
        if(pp)//通过lock()方法来判断它所管理的资源是否被释放
        {
            cout<<"sb use count:"<<pp.use_count()<<endl;
        }
    }
 
    ~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;
    }
    sa->doSomthing();
    cout<<"sb use count:"<<sb.use_count()<<endl;
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值