C++智能指针学习

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,817评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,329评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,354评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,498评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,600评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,829评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,979评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,722评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,189评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,519评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,654评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,940评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,762评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,993评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,382评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,543评论 2 349

推荐阅读更多精彩内容

  • C#、Java、python和go等语言中都有垃圾自动回收机制,在对象失去引用的时候自动回收,而且基本上没有指针的...
    StormZhu阅读 3,717评论 1 15
  • 原作者:Babu_Abdulsalam 本文翻译自CodeProject,转载请注明出处。 引入### Ooops...
    卡巴拉的树阅读 30,078评论 13 74
  • C++裸指针的内存问题有:1、空悬指针/野指针2、重复释放3、内存泄漏4、不配对的申请与释放 使用智能指针可以有效...
    WalkeR_ZG阅读 3,093评论 0 5
  • C++智能指针 原文链接:http://blog.csdn.net/xiaohu2022/article/deta...
    小白将阅读 6,859评论 2 21
  • 展信佳。 你好,抱歉拖了这么久才给你回信。我正在回家的火车上给你写这封信,不知你最近的生活有没有好一点。 先讲一个...
    小仙女fairy阅读 185评论 0 0