首页 > 其他分享 >RAII - std智能指针

RAII - std智能指针

时间:2024-10-12 22:48:43浏览次数:3  
标签:std 对象 RAII weak shared 共享 ptr 指针

std 智能指针

智能指针的选择标准

在类中使用智能指针作为成员变量。需要根据实际情况(主要是看所有权)

原始指针:

  • 所有权:该资源对象不属于我,

  • 使用前提:资源对象被释放前,我理应必然已经被释放。否则会存在风险。

unique_ptr:

  • 所有权:该对象仅仅属于我。

  • 被管理的资源对象的生命周期,取决于他所属的唯一一个引用的寿命。

shared_ptr:

  • 所有权:

    1. 该资源由多个对象共享

    2. 或者 虽然该对象仅仅属于我,但有使用 weak_ptr 的需要。

      可用于函数传参

  • 被管理的资源对象的生命周期,取决于所有引用中,最长寿的那一个。

weak_ptr:

  • 所有权:该资源对象不属于我

  • 该资源对象释放后,我仍可能不被释放时。

智能指针全家桶

相较于裸指针,智能指针对裸指针封装的初衷,是无需手动释放内存。

auto_ptr

auto_ptr 是c++ 98定义的智能指针模板,已经被C++11抛弃——支持复制运算符重载。

用于管理指针的对象,当对象过期时其析构函数将使用delete 来释放内存。

  • 复制或者赋值都会改变资源的所有权
  • 在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值
  • 不支持对象数组的内存管理

unique_ptr

使用 unique_ptr 最主要的特点:对资源是独占的,防止多个智能指针指向同一个对象。

  • 更安全

    • 不支持复制运算符重载,不允许复制。
    • 支持移动语义 move,完美转发 forward
    • 因为独占,所以压根就不存在shared_ptr循环引用的问题
  • 更好的支持数组。

    std::unique_ptr<T[]>:为数组提供自动内存管理的智能指针,可用于管理动态分配的数组的内存释放。

  • 通过在析构函数中释放资源来管理对象的生命周期,来自动管理资源。

shared_ptr

资源&控制块的生命周期

被管理的资源对象的生命周期:取决于所有 shared_ptr(强引用)中最长寿的那一个。

控制块的生命周期:

  • 所有的强引用和弱引用全都析构后,控制块才会删除。
  • 因为哪怕是资源析构掉,也要满足wp查询是否资源还存在的需求。
make_shared

make_shared内存布局更加紧凑,避免内存碎片化。

若使用shared_ptr<Person> (new X) :控制块的地址与被控制资源的地址 分离。

耗费资源高

耗费资源高,不能代替 unique_ptr

  • 需要维护一个 atomic 的引用计数器,效率低,需要额外的一块管理内存,访问实际对象需要二级指针

  • 而且 deleter 使用了类型擦除技术。

线程安全

对于这个问题,通常的回答我觉得答非所问:即 sharedptr 安全又不安全:

  1. 引用计数(控制块中的计数)线程安全

    资源的生命周期是线程安全的(这就足够啦)。

  2. 访问被指向的资源不是线程安全的(请用atomic_shared_ptr

    答非所问,人家的本职工作就是解决生命周期的问题。

    这个附加要求是在此之前的所有指针的通病。要解决它就必须引入新的复杂度。

如果想要支持这个需求2

  1. 在设计资源本身时下功夫

  2. 新写一个智能指针 支持指针控制资源的多线程修改,解决这个访问资源的竞争问题

    atomic_shared_ptr

反正不要影响sharedptr的复杂度和运行时效率,重温一下 cpp语言的准则:你不需要为不需要的特性付出代价!!

The zero-overhead principle is a C++ design principle that states:

  1. You don’t pay for what you don’t use.
  2. What you do use is just as efficient as what you could reasonably write by hand.
循环引用

内存泄漏

  • 全部用 shared_ptr,可能出现循环引用之类的问题,导致内存泄露,
  • 依然需要使用不影响计数的原始指针或者 weak_ptr 来避免。
#include <iostream>
#include <string>
#include <vector>
#include <memory>
 
using namespace std;
 
class Person {
  public:
    string m_sName;
    shared_ptr<Person> m_pMother;
    shared_ptr<Person> m_pFather;
    vector<shared_ptr<Person>> m_oKids;
    // vector<weak_ptr<Person>> m_oKids; //弱指针
 
    Person (const string& sName,
            shared_ptr<Person> pMother = nullptr,
            shared_ptr<Person> pFather = nullptr)
     : m_sName(sName), m_pMother(pMother), m_pFather(pFather) {
    }

    ~Person() {
      // 由于循环引用,shared_ptr<Person> pKid pMom pDad都不会调用析构函数
      cout << "删除 " << m_sName << endl;
    }
};
 
shared_ptr<Person> initFamily (const string& sName)
{
    shared_ptr<Person> pMom(new Person(sName + "的母亲"));
    shared_ptr<Person> pDad(new Person(sName + "的父亲"));
    shared_ptr<Person> pKid(new Person(sName, pMom, pDad));
    pMom->m_oKids.push_back(pKid);
    pDad->m_oKids.push_back(pKid);
    return pKid;
}
 
int main()
{
    string sName = "张三";
    shared_ptr<Person> pPerson = initFamily(sName);
 
    cout << sName << "家存在" << endl;
    cout << "- " << sName << "被分享" << pPerson.use_count() << "次" << endl;
    cout << "- " << sName << "母亲第一个孩子的名字是:"
         << pPerson->m_pMother->m_oKids[0]->m_sName << endl;
 
    sName = "李四";
    pPerson = initFamily(sName);
    cout << sName << "家已存在" << endl;
}

运行结果

张三家存在
- 张三被分享3次
- 张三母亲第一个孩子的名字是:张三
李四家已存在

weak_ptr

意义
解决循环引用

共享指针shared_ptr指针 —— 循环引用

如果两个对象使用shared_ptr指针相互引用,并且不存在对这些对象的其他引用,若要释放这些对象及其关联的资源,则共享指针shared_ptr不会释放数据,因为每个对象的引用计数仍为1。

解决:

  • 使用普通指针,需要手动管理相关资源的释放。

  • 使用 weak_ptr

  • unique_ptr

    • 对资源是独占的,防止多个智能指针指向同一个对象。

    • 从使用场景上就不存在循环引用的可能。但是,这个需求又确确实实存在。

生命周期

弱指针weak_ptr需要共享指针shared_ptr才能创建,是共享指针shared_ptr的辅助类。

不影响指向对象的生命周期

  • 每当拥有该对象的最后一个共享指针失去其所有权时,任何弱指针weak_ptr指向的资源都会自动变为空
  • (但影响shared_ptr的控制块的生命周期)

共享但不拥有对象 的意义

  • 若使用共享指针shared_ptr指针,则其将永远不会释放对象。

  • 若使用普通指针,则可能出现指针所引用的对象不再有效,这会带来访问已释放数据的风险。

  • weak_ptr 不影响引用对象的生命周期

    ——引用的生存期可以超过了所引用的对象的生命周期。

更小的开销

你不必为不需要的特性付出代价

The zero-overhead principle is a C++ design principle that states:

  1. You don’t pay for what you don’t use.
  2. What you do use is just as efficient as what you could reasonably write by hand.
使用

弱指针weak_ptr仅提供少量操作:创建,复制和赋值一个弱指针,将弱指针转换为共享指针,检查它是否指向对象。

创建

弱指针weak_ptr指针仅提供3个构造函数

  • default构造函数
  • copy构造函数
  • 共享指针shared_ptr的构造函数。
访问

lock()会从所包含的弱指针weak_ptr中产生一个共享指针shared_ptr。其shared_ptr对象的使用计数在共享指针的生命周期内会增加。

注意,要使用弱指针,需要在表达式中插入lock()函数:

pPerson->mother->kids[0].lock()->name

而不是调用

pPerson->mother->kids[0]->name

如果无法进行此修改(例如,由于该对象的最后所有者同时释放了该对象),lock()函数会生成一个空的shared_ptr,而直接调用运算符*或->将导致未定义的行为。

检查存在

检查弱指针指向的对象是否仍然存在,则可以使用以下几种方法:

  • 接口列表 expired()

如果弱指针weak_ptr不再共享对象(空的弱指针),则返回true。

此选项等效于检查use_count()是否等于0,但可能更快。

  • 调用 use_count()

返回关联对象的所有者(共享指针shared_ptr)的所有者数量,如果返回值为0,则不再有有效的对象。

但是请注意,通常只应出于调试目的调用use_count(),因为C++标准库明确指出:“use_count()不一定有效。”

  • 通过使用相应的共享指针shared_ptr构造函数将弱指针weak_ptr显式转换为共享指针shared_ptr

如果没有有效的引用对象,则此构造方法将引发bad_weak_ptr异常。

这个异常是从std::exception派生的类的异常,其中what()会返回“ bad_weak_ptr”。

操作字典

下表为弱指针提供的所有操作。

操作结果
weak_ptr<T> wp默认构造函数;创建一个空的弱指针
weak_ptr<T> wp(sp)创建一个弱指针,共享由sp拥有的指针的所有权
weak_ptr<T> wp(wp2)创建一个弱指针,共享由wp2拥有的指针的所有权
wp.~weak_ptr()析构函数;销毁弱指针,但对拥有的对象无效
wp = wp2赋值(wp之后共享wp2的所有权,放弃先前拥有的对象的所有权)
wp = sp用共享指针sp进行赋值(wp之后共享sp的所有权,放弃先前拥有的对象的所有权)
wp.swap(wp2)交换wp和wp2的指针
swap(wp1,wp2)交换wp1和wp2的指针
wp.reset()放弃拥有对象的所有权(如果有的话),并重新初始化为空的弱指针
wp.use_count()返回共享所有者的数量(拥有对象的shared_ptr数目);如果弱指针为空,则返回0
wp.expired()返回wp是否为空(等同于wp.use_count() == 0,但可能更快)
wp.lock()返回共享指针,该共享指针共享弱指针拥有的指针的所有权(如果没有共享指针,则为空共享指针)
wp.owner_before(wp2)提供严格的弱排序和另一个弱指针
wp.owner_before(sp)通过共享指针提供严格的弱排序

原先使用shared_ptr

shared_ptr<Person> m_pMother;
shared_ptr<Person> m_pFather;
vector<shared_ptr<Person>> m_oKids;

使用weak_ptr

shared_ptr<Person> m_pMother;
shared_ptr<Person> m_pFather;
vector<weak_ptr<Person>> m_oKids; //弱指针

shared_ptr<Person> pMom(new Person(sName + "的母亲"));
shared_ptr<Person> pKid(new Person(sName, pMom, pDad));
// 尽管是vector<weak_ptr<Person>>但可以push_back分享指针
pMom->m_oKids.push_back(pKid);

std 主类型和弱引用

延伸一下:shared_ptr 是主类型,weak_ptr是其弱引用。

// 主类型 std::shared_ptr<T>
// 弱引用类型 std::weak_ptr<T>、T *

除此之外,std库中还有其它已经实现且比较常用的 主类型 与 弱引用类型

// 主类型 std::string
// 弱引用类型 std::string_view、const char *

// 主类型 std::vector<T>
// 弱引用类型 std::span<T>、(T *, size_t)

// 主类型 std::unique_ptr<T>
// 弱引用类型 T *

标签:std,对象,RAII,weak,shared,共享,ptr,指针
From: https://blog.csdn.net/weixin_41733034/article/details/142890010

相关文章

  • fflush刷新缓冲和stderr无缓冲
    刷新缓存函数ffluash(FIFE*fp)       把库函数中的缓存内容强制写到内核中。无缓存函数stderr      只要用户调这个函数,就会将其内容写到内核中如图所示:这里使用fputs函数输出字符并配置为stdout(标准输出)但这里没有使用......
  • 双指针维护可交换结合贡献区间价值的正攻法
    对于一类区间价值V(l,r)=a[l]opta[l+1]opt...opta[r]当我们维护双指针同时需要维护内部区间的价值时,如果操作可交换结合并且可消去(存在y,xopty=0),l右移时直接去掉a[l]的价值即可;如果不可消去但可重复贡献(xoptx=x),可以使用ST表计算区间贡献;如果只满足结合律,我......
  • C++指针的基本使用
    目录一、定义和使用二、指针占用的空间三、空指针和野指针1、空指针2、野指针四、const修饰指针五、指针和数组六、指针和函数七、结构体指针一、定义和使用指针变量定义语法:数据类型*变量名;intmain(){ //1、指针的定义 inta=10;//定义整型变量a ......
  • 【C语言】语义陷阱(5):揭秘空指针与空字符串的微妙差异
    目录一、空指针(NullPointer)1.1.定义与表示1.2.用途1.3.安全性 1.4.注意事项1.5.空指针与野指针的区别1.5.1.特性对比1.5.2.安全性与风险1.5.3.编程实践二、指向空字符串的指针2.1.定义2.2.字符数组与空字符串2.3.指针的初始化2.4.空字符串的用途2......
  • go自动初始化结构体成员指针
    typeStustruct{Id*intId1*int32Id2*int64B*bool}func(this*TestBeanSuite)Test010_IniStru(){varstu=&Stu{}baseutils.InitStruNilPtrField(stu)golog.Info(stu)}2024-10-1116:49:16.190[INFO]{"Id&quo......
  • Idea android应用kotlin-stdlib-1.8.20 kotlin-stdlib-jdk81.6.21冲突
    Ideaandroid应用kotlin-stdlib-1.8.20kotlin-stdlib-jdk81.6.21冲突idea中开发android应用,安装android插件后,新建项目,然后各种包更新,最后运行时提示kotlin-stdlib-1.8.20kotlin-stdlib-jdk8:1.6.21冲突错误如下:FAILURE:Buildfailedwithanexception.Whatwentwrong:......
  • const与一级指针
    const与一级指针在C/C++中,const关键字用于表示一个变量的值是不可改变的。通常,它修饰离它最近的类型,意思是它所修饰的部分不能被修改。根据它在声明中的位置,const可以修饰指针或者指针所指向的值。1.const修饰变量如果const修饰变量,则该变量是常量,不能被修改。con......
  • 认识双指针
    基本概念双指针:通常是指在数组或链表中使用两个指针(索引)来遍历或操作数据结构。方向:指针可以同向移动(如快慢指针),也可以相向移动(如对撞指针)。应用场景:适用于需要遍历整个数据结构并进行某种操作的情况,如查找、排序、反转等。常见类型2.1快慢指针定义:两个指针从同一端开始移......
  • tour cpp: std::variant 实现无继承层次的访问者模式
    std::variant是基于模板而实现的一种包括了一个标志位的高级union对象;可以完全替代如下场景:structst{inttype;unionun{inti;floatf;};};#include<iostream>#include<variant>template<class...base>structoverloaded:bas......
  • C#项目传递图像指针到C++项目,并转换成cv::Mat图像
    一、C#传递指针地址到C++项目1、C++代码。新建C++/CLR.NetFramewrok4.8项目 .h文件#pragmaonce#include<opencv2/opencv.hpp>extern"C"__declspec(dllexport)intCropImage(cv::Mat&image,inth,intw);.cpp文件intCropImage(cv::Mat&image,inth,in......