专栏首页编程学习基地智能指针引用计数为0后,发生了什么?

智能指针引用计数为0后,发生了什么?

智能指针简介

为了解决C++内存泄漏的问题,C++11引入了智能指针(Smart Pointer)。在现代 c + + 编程中,标准库包含 智能指针,这些指针用于帮助确保程序不会出现内存和资源泄漏,并具有异常安全。C++11提供了三种智能指针:std::shared_ptr, std::unique_ptr, std::weak_ptr,使用时需添加头文件#include< memory >。

shared_ptr

shared_ptr 类型是 C++ 标准库中的一个智能指针,是为多个所有者可能必须管理对象在内存中的生命周期的方案设计的。shared_ptr 使用引用计数,每一个 shared_ptr 的拷贝都指向相同的内存。每引用它一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,删除所指向的堆内存。shared_ptr内部的引用计数是安全的,但是对象的读取需要加锁。

智能指针对比普通指针

智能指针的特殊之处在于帮助编程开发人员管理内存,确保程序不会出现内存和资源泄漏,并具有异常安全。

#include <iostream>
#include <memory>
using namespace std;
class Data
{
public:
    Data() {
        cout << "构造" << endl;
    }
    ~Data() {
        cout << "析构" << endl;
    }
};

int main()
{
    cout<<"智能指针:"<<endl;
    {
        std::shared_ptr<Data> Ptr(new Data);    //智能指针出了作用域就会被释放,引用计数减一
    }
    cout<<"普通指针:"<<endl;
    Data* ptr = new Data;
    delete ptr;
    return 0;
}
ubuntu@VM-16-5-ubuntu:~$ g++ -std=c++11 -o test test.cpp 
ubuntu@VM-16-5-ubuntu:~$ ./test 
智能指针
构造
析构
普通指针
构造
析构

从输出可以看出智能指针不需要手动释放,出了作用域之后就自动释放了,也就是说智能指针生命周期结束之后就会自动释放内存。而普通指针需要手动释放,不然会造成内存泄露。

基本用法

主要描述智能指针 shared_ptr 的创建,重置和管理对象的释放

#include <iostream>
#include <memory>
using namespace std;
class Data
{
public:
    Data(int id):m_id(id) {
        cout << m_id <<":构造" << endl;
    }
    ~Data() {
        cout << m_id << "析构" << endl;
    }
    int m_id; 
};
typedef std::shared_ptr<Data> DataPtr;
int main()
{
    {
        DataPtr dataPtr1 (new Data(1));              //new一个 shared_ptr 对象
        DataPtr dataPtr2 = std::make_shared<Data>(2);//构造一个 shared_ptr对象

        dataPtr1.reset();   //重置智能指针,释放其关联的指针内存
        // 首先new Data对象,然后智能指针引用计数减1,引用计数为0,故析构 Data(1),智能指针载指向 Data(3)
        dataPtr2.reset(new Data(3));
        cout<<"即将离开作用域"<<endl;
    }
    return 0;
}
ubuntu@VM-16-5-ubuntu:~$ ./test 
1:构造
2:构造
1析构
3:构造
2析构
即将离开作用域
3析构
main over

智能指针应用

主要描述智能指针的共享管理,共享之后引用计数加一, shared_ptr 的创建,获取管理的对象

#include <iostream>
#include <memory>
using namespace std;
class Data
{
public:
    Data(int id):m_id(id) {
        cout << m_id <<":构造" << endl;
    }
    ~Data() {
        cout << m_id << "析构" << endl;
    }
    int m_id; 
};
typedef std::shared_ptr<Data> DataPtr;
int main()
{
    {
        DataPtr dataPtr1(new Data(1));                         //new一个 shared_ptr 对象
        cout << "ptr1 conut:" << dataPtr1.use_count() << endl; //获取 dataPtr1 的引用计数
        {
            DataPtr dataPtr2 = dataPtr1;    // dataPtr2 和 dataPtr1 共享管理一个对象 dataPtr1 引用计数加一
            cout << "ptr1 conut:" << dataPtr1.use_count() << endl;
            cout << "ptr2 conut:" << dataPtr2.use_count() << endl;
            
            Data* pData = dataPtr2.get();   // 获取 shared_ptr 管理的对象,引用计数不变
            cout << "Data id:"<<pData->m_id << endl;
            cout<<"即将离开作用域"<<endl;
        }
        cout << "ptr1 conut:" << dataPtr1.use_count() << endl;
    }
    cout<<"main over"<<endl;
    return 0;
}
ubuntu@VM-16-5-ubuntu:~$ g++ -std=c++11 -o test test.cpp 
ubuntu@VM-16-5-ubuntu:~$ ./test 
1:构造
ptr1 conut:1
ptr1 conut:2
ptr2 conut:2
Data id:1
即将离开作用域
ptr1 conut:1
1析构
main over

智能指针引用计数为0,调用子类还是基类的析构?

在多肽里面,基类对象指向子类对象,对基类对象的delete操作不会执行子类析构,从而造成内存泄漏。那么由指针管理的基类对象(指向子类对象)的释放操作释放的是基类还是子类对象?

#include <vector>
#include <memory>
#include <iostream>
using namespace std;
class Father
{
public:
 Father(std::string name) {}
 ~Father() {cout<<"父类析构"<<endl;} //通过虚函数也可以解决这个内存泄漏问题
 std::string m_name;
};

typedef std::shared_ptr< Father > FatherPtr;

class Son:public Father
{
public:
 Son(std::string name) :Father(name) { m_name = name; }
 ~Son() { cout<<"子类析构"<<endl; }
};

typedef std::shared_ptr<Father> FatherPtr;
int main()
{
    {
        FatherPtr test(new Son("deroy"));
    }
    cout<<"-------------------------"<<endl;
    Father* Ptr = new Son("test");
    delete Ptr;
    return 0;
}
ubuntu@VM-16-5-ubuntu:~$ g++ -std=c++11 -o test test.cpp 
ubuntu@VM-16-5-ubuntu:~$ ./test 
子类析构
父类析构
-------------------------
父类析构

从输出上来看,智能指针 shared_ptr 管理的基类对象(指向子类对象)的释放操作释放的是子类对象,不会造成内存泄露

智能指针引用计数为0,你想干啥?

引用计数为0之后我不想智能指针来帮我释放内存,我想自己释放内存可以吗?智能指针结合匿名函数综合应用

#include <iostream>
#include <string>
#include <memory>

using namespace std;

class Person
{
public:
    Person(string name)
    {
        m_name = name;
    }
    ~Person(){
        std::cout << "父类析构:" << m_name << std::endl;
    }
    virtual void fun() { cout << m_name << ":我该干点什么事情好呢?" << endl; };

public:
    string m_name;
};
typedef std::shared_ptr<Person> PersonPtr;

class Student : public Person
{
public:
    Student(string name) : Person(name)
    {
        std::cout << "new:" << m_name << std::endl;
    }
    ~Student(){
        std::cout << "子类析构:" << m_name << std::endl;
    }
    virtual void fun() { cout << m_name << ":我该干点什么事情好呢?" << endl; };
};

PersonPtr createPerson( string name )
{
    #if 1
    PersonPtr ptr = PersonPtr(new Student(name), [](void *val) {
        Student *p = (Student *)(val);
        if (p)
        {
            cout<<"use_count=0,call lambda release function"<<endl;
            p->fun();
            delete p;
        }
    });
    #else
        PersonPtr ptr = PersonPtr(new Student(name));
    #endif
    return ptr;
}

int main()
{
    {
        PersonPtr pStu = createPerson("deroy");
        cout<<"即将离开作用域"<<endl;
    }
    cout << "main over" << endl;
    return 0;
}
ubuntu@VM-16-5-ubuntu:~$ g++ -std=c++11 -o test test.cpp 
ubuntu@VM-16-5-ubuntu:~$ ./test 
new:deroy
即将离开作用域
use_count=0,call lambda release function
deroy:我该干点什么事情好呢?
子类析构:deroy
父类析构:deroy
main over

注意事项

  1. 智能指针管理的是堆上面的指针,(栈上面的地址会造成两次调用析构)
  2. shared_ptr相当于一个指针,拷贝和赋值会是的引用加一
std::shared_ptr<Data> dataPtr1(new Data(1)); // Data(1)的引用计数为1
std::shared_ptr<Data> dataPtr2 = dataPtr1;  //现在dataPtr1和dataPtr2同时指向Person(1),的引用计数加一
std::cout<<dataPtr1.use_count()<<std::endl; //引用计数为2
std::cout<<dataPtr2.use_count()<<std::endl; //引用计数为2
  1. 只有当引用计数为0时,才会释放内存
/*接上面的代码*/
dataPtr1.reset(); //Data(1)的引用计数为1
//dataPtr2.reset();//Data(1)的引用计数为0,Data(1)
  1. 不要用一个原始指针初始化多个shared_ptr,原因在于,会造成二次销毁,如下所示:
#include <iostream>
#include <memory>
using namespace std;
class Person
{
public:
    Person(int v) {
        value = v;
        std::cout << "Cons" <<value<< std::endl;
    }
    ~Person() {
        std::cout << "Des" <<value<< std::endl;
    }
    int value;
};

int main()
{
    Person *p5 = new Person(5);
    std::shared_ptr<Person> p6(p5);
    std::shared_ptr<Person> p7 = p6; //使用赋值共享指针对象的管理
    // std::shared_ptr<Person> p7(p5);// logic error
    return 0;
}
ubuntu@VM-16-5-ubuntu:~$ g++ -std=c++11 -o test test.cpp 
ubuntu@VM-16-5-ubuntu:~$ ./test 
Cons5
Des5
  1. 避免循环引用

后面 weak_ptr 介绍。

智能指针相关的函数

成员函数

作用

reset()

重置智能指针,delete其关联的指针。

release()

不delete关联指针,并返回关联指针。 释放关联指针的所有权,智能指针为空。

get()

仅仅返回关联指针

use_count()

获取引用计数

std

作用

std::make_shared

创建 make_shared 对象C++14

std::move()

对象转移

·················END·················

本文分享自微信公众号 - 编程学习基地(LearnBase),作者:DeRoy

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2021-05-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • UE4智能指针

    提供AsShared方法获取共享指针, 凡是派生自TSharedFromThis的对象请通过AsShared获取智能指针, 可以保证引用计数器的一致.

    小伏羲
  • Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析【转】

    Android系统的运行时库层代码是用C++来编写的,用C++ 来写代码最容易出错的地方就是指针了,一旦使用不当,轻则造成内存泄漏,重则造成系统崩溃。不过系统为...

    233333
  • Android智能指针

    网上已经有很多分析智能指针的文章了,讲得不错的是:Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析。本文尽量从不分析代码的角度,将And...

    用户2930595
  • C++11智能指针

    内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存...

    海盗船长
  • TarsCpp 组件 之 智能指针详解

    在 C++ 中,内存管理是十分重要的问题,一不小心就会造成程序内存泄露,那么怎么避免呢?通过智能指针可以优雅地管理内存,让开发者只需要关注内存的申请,内存的释放...

    TARS基金会
  • [c/c++后台开发面经] 京东面经(含答案)

    1000瓶无色无味的药水,其中有一瓶毒药,10只小白鼠拿过来做实验。喝了无毒的药水第二天没事儿,喝了有毒的药水后第二天会死亡。如何在一天之内(第二天)找出这瓶有...

    C语言与CPP编程
  • C++智能指针

    C++中,动态内存的管理是通过一对运算符来完成的,new用于申请内存空间,调用对象构造函数初始化对象并返回指向该对象的指针。delete接收一个动态对象的指针,...

    Dabelv
  • 通俗易懂学习C++智能指针

    智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。

    海盗船长
  • C++ 新特性学习(一) -- 概述+智能指针(smart_ptr)

    C++ 0x/11 终于通过了,真是个很爽的消息。于是乎我决定对新的东西系统学习一下。

    owent

扫码关注云+社区

领取腾讯云代金券