c++

C++智能指针学习

2019-08-11  本文已影响0人  tbmichael

C++智能指针学习

[toc]

智能指针内存管理要解决的根本问题是:一个堆对象,在被多个对象引用时,如何释放资源的问题。

shared_ptr

shared_ptr智能指针内存管理的思路

shared_ptr智能指针的解决思路:最后一个引用它的对象被释放时,释放这段内存。

实现方法:对 被管理的资源 进行计数。当一个sharedptr对象要共享这个资源的时候,该资源的引用计数加1,当该对象生命周期结束了,再把该引用计数减1。这样,当最后一个引用它的对象被释放的时候,资源的引用计数减少到0,此时释放该资源。

智能指针的使用

智能指针的内部实现

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

智能指针拷贝构造的原理

image.png

reset

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

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

如果p是唯一指向其对象的shard_ptr, reset会释放对象。

如果传递了可选参数内指针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

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

unique_ptr

概念

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

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

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
}

循环引用计数问题

https://blog.csdn.net/daniel_ustc/article/details/23096229

问题复现

image.png
#include <iostream>
using namespace std;

namespace cycle_ref {
class B;
class A {
public:
    std::shared_ptr<B> spb;
    void dosomething() {
        
    }
    ~A() {
        cout << "A dtor" << endl;
    }
};
    
class B {
public:
    std::shared_ptr<A> spa;
    ~B() {
        cout << "B dtor" << endl;
    }
    
};
}
int main(int argc, const char * argv[]) {
    {
        using namespace cycle_ref;
        shared_ptr<cycle_ref::A> sa(new cycle_ref::A());
        shared_ptr<cycle_ref::B> sb(new cycle_ref::B());
        
        if (sa && sb) {
            sa->spb = sb;
            sb->spa = sa;
        }
        
        cout << "sa user_count:" << sa.use_count() << endl;
        cout << "sb user_count:" << sb.use_count() << endl;
    }
}

代码运行结果:

sa user_count:2
sb user_count:2

问题:并没有调用析构函数,说明内存并没有被释放,有内存泄漏。
原因:
当程序退出时,系统会调用sb和sa的析构函数,析构函数会递减引用计数。
如果count为0,则删除该内存;否则不删除内存。

这里count为2,递减了计数器后,count仍然不为0,则不删除内存对象,自然调用对象的析构函数。

用weak_ptr破局---变shared_ptr为weak_ptr

image.png
namespace cycle_ref {
class B;
class A {
public:
//    std::shared_ptr<B> spb;
    std::weak_ptr<B> spb;
    void dosomething() {
        std::shared_ptr<B> pb = spb.lock();
        if (pb) {
            cout << "in dosomething: sb use_count:" << pb.use_count() << endl;
        }
    }
    ~A() {
        cout << "A dtor" << endl;
    }
};

class B {
public:
    std::shared_ptr<A> spa;
    ~B() {
        cout << "B dtor" << endl;
    }
    
};
}

int main(int argc, const char * argv[]) {
    {
        using namespace cycle_ref;
        shared_ptr<cycle_ref::A> sa(new cycle_ref::A());
        shared_ptr<cycle_ref::B> sb(new cycle_ref::B());
        
        if (sa && sb) {
            sa->spb = sb;   //因为spb是weak_ptr,因此这里不会递增sb的引用计数
            sb->spa = sa;
        }
        sa->dosomething();
        cout << "sa use_count:" << sa.use_count() << endl;
        cout << "sb use_count:" << sb.use_count() << endl;
    }
}

代码运行结果:

in dosomething: sb use_count:2
sa use_count:2
sb use_count:1
B dtor
A dtor

解释:

  1. dosomething函数中,spb的引用计数为2。因为其中的lock函数返回了一个智能指针sb
  2. 因为使用了weak_ptr,所以sb的引用计数为1。
  3. 析构流程如下:
    1. 函数退出时,先析构sb对象,释放B对象,析构spa对象,递减sa的引用计数为1,
    2. 再析构sa对象,释放A对象。

reference

http://www.cppblog.com/Solstice/archive/2013/01/28/197597.html

上一篇下一篇

猜你喜欢

热点阅读