首页 > 其他分享 >智能指针

智能指针

时间:2023-01-06 22:55:18浏览次数:35  
标签:AA std aa 智能 共享 ptr 指针

智能指针

std::auto_ptr

#include <iostream>
#include <memory>

int main()
{
    std::auto_ptr<int> ptr(new int);
    std::auto_ptr<int> ptr1 = ptr;

    std::cout << ptr.get() << std::endl;
    std::cout << ptr1.get() << std::endl;

    return 0;
}

std::auto_ptr的主要功能是实现自动的资源管理。

在上述操作后,ptr的值为空,也即指针的拷贝将指针的值改变了,这和普通指针的拷贝语义是不一致的。C++11标准中已将std::auto_ptr标记为弃用。

std::shared_ptr

共享指针,其支持自动资源管理并实现了拷贝的语义。

使用共享指针管理对象时,需要注意同一个对象只能由一个共享指针及其拷贝来管理,否则就会出现多次释放

共享指针引起泄漏的情况

使用共享指针管理对象时,需要注意避免循环引用。若两个对象均通过共享指针持有另一个对象,则由于循环引用在离开作用域时无法自动释放造成资源泄漏

循环引用的一种特殊形式是自引用,也即一个对象通过共享指针持有自己的指针。

循环引用的例子:

#include <iostream>
#include <memory>

class BB;

class AA
{
public:
    std::shared_ptr<BB> ptr_bb;
    AA() {std::cout << "AA +" << std::endl;}
    ~AA() {std::cout << "AA -" << std::endl;}
};

class BB
{
public:
    std::shared_ptr<AA> ptr_aa;
    BB() {std::cout << "BB +" << std::endl;}
    ~BB() {std::cout << "BB -" << std::endl;}
};

int main()
{
    std::shared_ptr<AA> ptr_aa(new AA);
    std::shared_ptr<BB> ptr_bb(new BB);
    ptr_aa->ptr_bb = ptr_bb;
    ptr_bb->ptr_aa = ptr_aa;

    return 0;
}

执行结果如下:

./a.out
AA +
BB +

自引用的例子如下:

#include <iostream>
#include <memory>

class AA
{
public:
    std::shared_ptr<AA> ptr_bb;
    AA() {std::cout << "AA +" << std::endl;}
    ~AA() {std::cout << "AA -" << std::endl;}
};

int main()
{
    std::shared_ptr<AA> ptr_aa(new AA);
    ptr_aa->ptr_bb = ptr_aa;

    return 0;
}

执行结果如下:

./a.out
AA +

循环引用引起泄漏的原因(技术角度)

首先从技术角度来分析引起泄漏的原因。

共享指针内部是通过引用计数来实现对资源的管理的。每当共享指针被复制一次,计数加1,每当共享指针被析构,计数减1,当计数被减至0时,才真正执行被管理对象的销毁。值得注意的是,共享指针的计数对于拷贝得到的共享指针是相同的(内部实现上,通过拷贝得到的共享指针内部均有一个指向同一个计数值的指针)。

先看一个正常的示例:

#include <memory>
#include <iostream>

class AA
{
public:
    AA() {std::cout << "AA +" << std::endl;}
    ~AA() {std::cout << "AA -" << std::endl;}
};

int main()
{
    std::shared_ptr<AA> ptr_aa(new AA());
    std::shared_ptr<AA> ptr_aa_copy;

    ptr_aa_copy = ptr_aa;

    return 0;
}

主函数中先创建了一个共享指针ptr_aa,管理一个通过new创建的对象AA。然后创建了一个共享指针ptr_aa_copy,并通过拷贝赋值的方式成为了ptr_aa的拷贝,因此这两个共享指针的引用计数均为2。在离开main函数时,一共有两个对象需要被销毁,因此引用计数最终变为0,即最终会完成资源的销毁。

以下为出现泄漏的循环引用的main函数部分:

int main()
{
    std::shared_ptr<AA> ptr_aa(new AA);
    std::shared_ptr<BB> ptr_bb(new BB);

    ptr_aa->ptr_bb = ptr_bb;
    ptr_bb->ptr_aa = ptr_aa;

    return 0;
}

在这段代码中,首先创建了两个共享指针,然后对这两个共享指针分别执行了一次拷贝,即这两个共享指针的引用计数均为2。在主函数结束时,这两个共享指针需要销毁,因此这两个共享指针对象的析构函数将被执行(注意,不是AA对象和BB对象的析构函数)。在执行ptr_aa共享指针对象的析构函数时,引用计数将由2减为1,由于引用计数不为0,其持有的AA对象不会被销毁,同样在ptr_bb共享指针对象销毁的时候,其持有的BB对象也不会被销毁。因此造成了内存的泄漏。

循环引用使得在离开作用域时,所有的共享指针的引用计数均没有降到0,从而所有被持有的对象均没有被析构。

循环引用引起泄漏的原因(语义角度)

共享指针std::shared_ptr会持有一个对象,这意味着多个共享指针持有同一个对象时,只要有一个共享指针没有被销毁时,这个被持有的对象就不会被销毁。这种持有的方式称为强引用,与其对应的是弱引用,普通的指针是一种弱引用,C++也提供了std::weak_ptr用于弱引用。

如果AA对象持有一个BB对象的共享指针,BB对象持有一个CC对象的共享指针,依次类推,则产生了CC对象的释放依赖BB对象的释放,BB对象的释放依赖AA对象的释放的这种依赖链。很容易想到,如果在程序中产生了这种依赖链,则必须将整个依赖关系设计为一个有向无环图(DAG),这样才能确保正确的释放。

循环引用的破解之法

厘清持有关系和引用关系

持有关系使用强引用,引用关系使用弱引用。

一个例子是二叉树的节点,其中三个指针leftrightparent。由于我们需要通过上层节点去管理下层节点,因此leftright引用为强引用,parent引用为弱引用。

避免依赖成环

如果一个对象持有另一个对象的共享指针,就需要注意形成的依赖关系是否成环。

下面分析一个正常的依赖的释放流程:

#include <memory>
#include <iostream>

class AA;
class BB;
class CC;

class AA
{
public:
    AA() {std::cout << "AA +" << std::endl;}
    ~AA() {std::cout << "AA -" << std::endl;}
    std::shared_ptr<BB> bb;
};

class BB
{
public:
    BB() {std::cout << "BB +" << std::endl;}
    ~BB() {std::cout << "BB -" << std::endl;}
    std::shared_ptr<CC> cc;
};

class CC
{
public:
    CC() {std::cout << "CC +" << std::endl;}
    ~CC() {std::cout << "CC -" << std::endl;}
};

int main()
{
    std::shared_ptr<AA> a(new AA());
    std::shared_ptr<BB> b(new BB());
    std::shared_ptr<CC> c(new CC());

    a->bb = b;
    b->cc = c;

    return 0;
}

这里的依赖关系为AA->BB->CC,没有形成环状依赖。main函数中一共有三个共享指针,其中aa的引用计数为1,bbcc的引用计数为2。当离开main函数时,aabbcc的析构函数均被执行。当aa的析构函数执行时,引用计数减为0,其持有的AA对象被析构,从而引起其成员bb被析构,从而b的引用计数降为1(注意,持有同一个对象的共享指针共享同一个引用计数)。当bb的析构函数执行时,引用计数减为0,其持有的BB对象被析构,从而引起其成员cc被析构,c的引用计数降为1。最终cc的析构函数执行时,引用计数减为0,其持有的CC对象被析构。全部对象被析构完毕。

因此最终执行结果为:

./a.out
AA +
BB +
CC +
AA -
BB -
CC -

标签:AA,std,aa,智能,共享,ptr,指针
From: https://www.cnblogs.com/amazzzzzing/p/17031799.html

相关文章

  • c++ 指针与引用
    引用很易与指针混淆,它们之间有三个主要的不同:不存在空引用。引用必须连接到一块合法的内存。一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候......
  • 关于指针做函数参数-》指针的值传递和引用传递
    上图中,指针q在传入setnull函数时,将q赋给了setnull的一个临时拷贝!因此在setnull函数中令t=nullptr时,并不会影响到主程序中的q。从指针的地址也可以看出,setnu......
  • 指针参数和返回值指针相关问题
    指针参数和返回值指针相关问题一、前言我们知道普通变量作为参数传入函数是无法修改变量的值,如下示例就是典型的voidswap(intx,inty){inttemp=x;x=y;......
  • 2022.01.21.类成员函数指针
    在继承下,允许未经转换,父类的指针可以指向子类指针父类的指针可以指向子类对象:同意多种类型,提高复用性,扩展性在调用继承于同一个父类的多个子类的同一个函数时,可以使用父......
  • 交投智能 & JNPF:构建全场景流程中台,助力企业综合办公一体化
    互联网时代,数字化全面赋能企业组织,可以提高业务执行效率、提升企业总体运营效能、节约成本等,帮助企业灵活自如地应对外部环境的变化,数字化企业是实现转型升级的最佳解决方......
  • 初识指针
    指针是什么指针和指针类型野指针指针运算指针和数组二级指针指针数组自学b站“鹏哥C语言”笔记。一、指针是什么指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑......
  • C | 指针
    1.什么是指针指针是一种变量,也称指针变量,它的值不是整数、浮点数和字符,而是内存地址。指针的值就是变量的地址,而变量有拥有一个具体值。因此,可以理解为变量直接引用了一个......
  • C语言指针常见问题
    我们在学C语言时,指针是我们最头疼的问题之一,针对C语言指针,博主根据自己的实际学到的知识以及开发经验,总结了以下使用C语言指针时常见问题。指针指针做函数参数学习......
  • WebStorm——最智能的Javascript IDE
    ​ WebStorm是什么?WebStorm是JetBrains的一个专门为Web开发人员设计的IDE,JetBrains大家应该不陌生,Resharper、IntelliJIDEA等都是出自这个公司。JetBrains给WebStorm下的......
  • 让驾驶随心而动 浪潮信息助力纵目科技加速智能驾驶研发
    没有方向盘的约束,无需时刻绷紧神经,只要下达指令即可享受轻松愉悦的旅程……自动驾驶已经成为交通领域极具想象力与商业价值的技术之一。但是,要想让自动驾驶的愿景真正变成现......