首页 > 编程语言 >C++中的共享指针以及循环引用

C++中的共享指针以及循环引用

时间:2023-02-07 22:01:27浏览次数:59  
标签:std bar C++ 引用 shared 共享 foo ptr 指针

C++中shared_ptr对象之间可以共享对象的拥有权,但是这种共享的对象引用在某些情况下可能会引发一些问题。例如,循环引用会造成两个对象之间相互引用,无法删除对象。

一个循环引用的例子

class bar;

class foo
{
public:
    
    foo()
    {

    }
    
    std::shared_ptr<bar>  ptr_bar;    
        
    ~foo()
    {
        std::cout << "foo destructed"<<std::endl;
    }
};

class bar
{
public:
    
    bar()
    {

    }
    
    std::shared_ptr<foo> ptr_foo;

    ~bar()
    {
        std::cout << "bar destructed" << std::endl;
    }
};


int main(int argc,const char* argv[])
{
    std::shared_ptr<foo> f = std::make_shared<foo>();
    std::shared_ptr<bar> b = std::make_shared<bar>();
    f->ptr_bar = b;
    b->ptr_foo = f;
    std::cout << "foo's count is " << f.use_count() << std::endl;
    std::cout << "bar's count is " << b.use_count() << std::endl;
}

//output
//foo's count is 2
//bar's count is 2

可以看到,并没有出现析构器中应该打印的信息,这说明我们构造的对象并没有被析构,那么内存便泄漏了。那么造成该现象的原因是什么呢?

std::shared_ptr使用了引用计数算法,每当一个std::shared_ptr对象引用一个对象,那么会在对应的控制块中将引用计数加一,当std::shared_ptr对象被析构时,引用计数会减一。如果计数等于零,那么将会删除该对象。

在main函数里面,我们通过std::make_shared函数分别构造了std::shared_ptr和std::shared_ptr对象,此时foo对象和bar对象的引用计数都为一,接着我们通过拷贝赋值运算符,将b和f分别都赋值给了foo对象和bar对象的ptr_bar和ptr_foo数据成员。std::shared_ptr的拷贝赋值运算符的含义是共享对象的拥有权,所以,此时foo和bar对象的引用计数都为二,这也就是打印出来的计数为二的原因。

当退出main函数的时候,f和b对象都会析构。f和b对象析构时都会将引用计数减一。这里就是问题所在,此时foo对象和bar对象的引用计数为一,无法达到零,导致foo对象和bar对象永远无法被删除。

利用std::weak_ptr打破循环引用

上述的循环引用可以通过std::weak_ptr来解决。修改的代码如下

class bar;


class foo
{
public:
    
    foo()
    {

    }
    
    std::weak_ptr<bar>  ptr_bar;    
    
    ~foo()
    {
        std::cout << "foo destructed"<<std::endl;
    }
};

class bar
{
public:
    
    bar()
    {

    }
    
    std::weak_ptr<foo> ptr_foo;

    ~bar()
    {
        std::cout << "bar destructed" << std::endl;
    }
};

int main(int argc,const char* argv[])
{
    std::shared_ptr<foo> f = std::make_shared<foo>();
    std::shared_ptr<bar> b = std::make_shared<bar>();

    f->ptr_bar = b;
    b->ptr_foo = f;
    std::cout << "foo's count is " << f.use_count() << std::endl;
    std::cout << "bar's count is " << b.use_count() << std::endl;
}

//output
//foo's count is 1
//bar's count is 1
//bar destructed
//foo destructed

可以看到std::weak_ptr并没有给引用计数加一,实际上它增加的是控制块的弱引用计数。当std::weak_ptr的析构器被调用的时候,它会将弱引用计数减一,如果弱引用计数等于零,那么会删除控制块。其实也不一定需要foo和bar类中都是std::shared,只要其中之一是std::weak_ptr即可。

标签:std,bar,C++,引用,shared,共享,foo,ptr,指针
From: https://www.cnblogs.com/riasartemis/p/17099954.html

相关文章

  • C++编程中遇到的问题
    函数中无法通过sizeof运算得到数组的大小及维数,因为,数组形参实际上是指针类型。#include<iostream>usingnamespacestd;//数组作为参数时,退化为简单的指针voidg......
  • C++ Day14 借助智能指针实现文本查询查询
    一、设计思路数据结构:1、读取文件时,要记住文件的每一行,并且要将每一行分解为独立的单词vector<string>vec;istringstream2、输出时提供每个单词与其关联的行号,且......
  • C++中的值类别与模板推导
    C++中表达式的值类别C++中的表达式有两个属性,分别是值类型和值类别,本篇文章着重于表达式的值类别。C++中表达式的值类别分为以下几种泛左值(glvalue,generalizedlvalue):这......
  • c++常用知识点复盘
    、Linux内存管理机制内存全貌图:  Linux内存分为用户态和内核态两种,以32位4G的Linux内存为例进行说明,其区别如下:用户态:Ring3运行于用户态的代码......
  • 【C++复习】模板与群体数据(2)
    学习重点:容器类型内部的实现机制,顺便复习前面各章内容。容器类型的具体实现不需要特别关注(目前不需要会裸手写这么一个容器类型)1、群体/线性群体群体的概念群体是指由......
  • 【八大数据排序法】基数排序法的图形理解和案例实现 | C++
    第二十章基数排序法:::hljs-center目录第二十章基数排序法●前言●认识排序●一、基数排序法是什么?1.简要介绍2.图形理解3.算法分析●二、案例实现1.......
  • c++ gstreamer使用2
    1,播放教程playbin#include<gst/gst.h>#include<stdio.h>/*Structuretocontainallourinformation,sowecanpassitaround*/typedefstruct_CustomData......
  • [C++]数据结构之堆-上滤下滤以及用于排序
    #include<iostream>usingnamespacestd;/**堆,就是一棵完全二叉树,物理存储方式是数组,一般情况下,都牺牲第一个元素arr[0],剩下的就满足了从1开始计数*若堆从1开始计数,那么......
  • 【C++复习】5.7 多文件结构与编译预处理命令
    1、C++项目结构C++程序的一般组织架构类声明文件(.h文件)类实现文件(.cpp文件)类的使用文件(main()所在的.cpp文件)用工程组合各文件2、编译链接编译链接过程3、外部......
  • C++ 位运算
    位运算基本符号:& 按位与     &=按位与赋值| 按位或       |= 按位或赋值^ 按位异或   ^= 按位异或赋值<< 左移    <<= ......