6.21 移动语义与智能指针

news2024/11/23 23:37:15
    //先构造,再拷贝构造
    //利用"hello"这个字符串创建了一个临时对象
    //并复制给了s3
    //这一步实际上new了两次
    String s3 = "hello";

背景需求: 这个隐式创建的字符串出了该行就直接销毁掉,效率比较低

可以让_pstr指向这个空间,可以让匿名对象的指针指向nullptr,这样析构函数判断是nullptr对于匿名对象就不会处理。

但是不能直接在拷贝构造函数中直接进行操作,因为可能有的并不是匿名对象的情况,直接将原本的销毁不合适

能对表达式取地址的,称为左值;不能取地址的,称为右值。

&1;//error取不到地址

&"hello"; //文字常量区可以取地址(补充如果文字是相同的会存在一个地方)

&(a + b);//error
&String("hello"); // error

右值存储的位置:

右值可以存储在内存中,比较复杂的或者放内存中性能更佳的时候。

右值也可以存在寄存器中,简单的变量往往放在寄存器中。

区分左右值不能通过存储位置和等号左右进行判断

非const左值引用只能绑定到左值,不能绑定到右值,也就是非const左值引用只能识别出左值。

const左值引用既可以绑定到左值,也可以绑定到右值,也就是表明const左值引用不能区分是左值还是右值。

所以接着上面就是我们需要确定只要是右值引用就可以使用特别的右值拷贝构造,而左值就是普通的拷贝构造函数。

右值引用:

右值引用不能绑定到左值,但是可以绑定到右值,也就是右值引用可以识别出右值

 String(String && rhs)
    : _pstr(rhs._pstr)//浅拷贝
    {
        cout << "String(String&&)" << endl;
        rhs._pstr = nullptr;
    }

也就是说在有移动构造的时候,对于临时变量就不再调用以前的复制运算符函数

加上编译器的去优化参数 -fno-elide-constructors

发现没有再调用拷贝构造函数,而是调用了移动构造函数。

如果还没有就再加上 cstd = c++11

移动构造函数的特点:

1. 四大金刚只要是显示定义一个的时候,编译器就不再提供移动构造函数

2. 显示定义了拷贝构造没有移动构造,就会调用拷贝构造(临时)

3. 显示定义了移动构造,右值复制使用移动构造。

总结:移动构造函数优先级高于拷贝构造函数。也就是会尝试移动函数不行再常规

移动赋值函数

String & operator=(String && rhs){
  if(this != &rhs){
      delete [] _pstr;
      //浅拷贝
      _pstr = rhs._pstr;
      rhs._pstr = nullptr;
      cout << "String& operator=(String&&)" << endl;
  }
  return *this;
}

其实这地方还有有点容易混淆的,因为

String s = "hello"; 移动构造函数

String s = String("hello");移动构造函数 

s = String("xixi")        移动赋值运算符函数

移动赋值运算符函数特点和移动拷贝构造函数相同。

总结:

拷贝构造和赋值运算函数具有复制控制语义的函数

移动构造和移动复制有移动语句的函数移交控制权(移交控制权)

移动语义函数优于控制语义函数

    String s1("hello");
    //右值复制给左值,肯定不是同一个对象
    s1 = String("world");
    //创建了两个内容相同的临时对象,也不是同一对象
    String("wangdao") = String("wangdao");

std::move函数

背景需求:有时候可能必须需要用到右值,所以需要将左值转换为右值

   int a = 1;
   &(std::move(a)); //error,左值转成了右值
   int && r = std::move(a);

【注意】

值显式转换为右值后,原来的左值对象就无法正常工作了,必须要重新赋值才可以继续使用。

但是通过输出流运算符输出s1的 _pstr依然造成了程序的中断,所以说明对std::move(s1)的内容进行修改,会导致s1的内容也被修改。

std::move的本质是在底层做了强制转换(并不是像名字表面的意思一样做了移动)

这就更是说明了move只是欺骗系统说这是一个右值,让系统当作一个右值处理,并且将控制权也都交了出去,对于这个‘右值’的处理就是对于本身的处理。

【注意】在这个时候可能在移动赋值函数中,如果是使用move导致的(如果去掉自复制判断)自复制情况下就会出现问题,所以必须加上自复制判断。

String & operator=(String && rhs){
  if(this != &rhs){
      delete [] _pstr;
      //浅拷贝
      _pstr = rhs._pstr;
      rhs._pstr = nullptr;
      cout << "String& operator=(String&&)" << endl;
  }
  return *this;
}

右值引用本身的性质

int && func(){
    return 10;
}

void test1(){
    // &func();  //无法取址,说明返回的右值引用本身也是一个右值
    int && ref = func();
    &ref;  //可以取址,此时ref是一个右值引用,其本身是左值
}

注意区分下列的情况。

第一二种情况,返回的都是副本临时值都是不能取地址的,第三种情况是全局变量的引用可以取地址,第四种情况是返回一个右值引用是不可以取地址的,但是如果是是在程序中定义的一个右值引用的话是可以进行取地址操作的,原因就是下述所讲有名字的右值引用是左值,无名字是右值。

有名字右值引用是左值,没有名字还是左值

String str2("wangdao");
String func2(){
    String str1("wangdao");
	str1.print();
    return str1;    //对于一个将亡的对象而言是调用移动构造
    //return str2;//长期存在的就调用拷贝构造函数
}

void test2(){
    func2();
    //&func2(); //error,右值
   	String && ref = func2();
    &ref;  //右值引用本身为左值
}

在上述代码中return语句是调用移动构造语句,而不是拷贝构造语句

return

总结:返回将亡值对象使用移动构造,否则调用拷贝构造

资源管理

背景需求:程序的出口比较多,如果在某一个出口忘记将资源释放的时候,不能很好的将资源释放

所以说想到可以通过一个类进行封装保护实现,就像是运算符重载一章中的middleLayer

class SafeFile
{
public:
    //在构造函数中初始化资源(托管资源)
    SafeFile(FILE * fp)
    : _fp(fp)
    {
        cout << "SafeFile(FILE*) " << endl;
    }
    //提供方法访问资源
    void write(const string & msg){
        fwrite(msg.c_str(),1,msg.size(),_fp);
    }
    //利用析构函数释放资源
    ~SafeFile(){
        cout << "~SafeFile()" << endl;
        if(_fp){
            fclose(_fp); 
            cout << "fclose(_fp)" << endl;
        }
    }
private:
    FILE * _fp;
};

void test0(){
    string msg = "hello,world";
    SafeFile sf(fopen("wd.txt","a+"));
    sf.write(msg);
}

同时也像是middleLayer中的担心会出现多次释放的情况所以也是直接在创建对象的时候就是用fopen而不是再赋给一个有名的指针。

RAII技术

RAII类的常见特征

RAII常见的特征:

1.构造函数中托管资源,析构中释放资源

2.不允许复制和赋值(对象语义)

3.提供若干访问方法 (->*读写)

值语义:可以进行复制或赋值(两个变量的值可以相同)

对象语义:不允许复制或者赋值

【联系】上面刚说了移动语义和赋值控制语义

常用手段:

  1. 将拷贝构造函数与赋值运算符函数设置为私有的
  2. 将拷贝构造函数与赋值运算符函数=delete
  3. 使用继承的思想,将基类的拷贝构造函数与赋值运算符函数删除(或设为私有),让派生类继承基类。

 使用最保险的就是将函数删除的方法

RAII类的模拟

借助于上面对于智能指针思想的描述的实现。 

template <class T>
class RAII
{
public:
    //1.在构造函数中初始化资源(托管资源)
    RAII(T * data)
    : _data(data)
    {
        cout << "RAII(T*)" << endl;
    }

    //2.在析构函数中释放资源
    ~RAII(){
        cout << "~RAII()" << endl;
        if(_data){
            delete _data;
            _data = nullptr;
        }
    }

    //3.提供若干访问资源的方法
    T * operator->(){
        return _data;
    }
    
    T & operator*(){
        return *_data;
    }

    T * get() const{
        return _data;
    }

    void set(T * data){
        if(_data){
            delete _data;
            _data = nullptr;
        }
        _data = data;
    }

    //4.不允许复制或赋值
    RAII(const RAII & rhs) = delete;
    RAII& operator=(const RAII & rhs) = delete;
private:
    T * _data;
};

智能指针

//<memory>头文件中

//std::auto_ptr         c++0x 不安全在c++17中已经丢弃

//std::unique_ptr    c++11

//std::shared_ptr     c++11

//std::weak_ptr        c++11

auto_ptr

在其中auto_ptr的赋值运算符函数和拷贝构造函数是可以使用的。

auto_ptr的拷贝构造实际上却是移动复制

template <class _Tp> 
class auto_ptr {
public:
    //拷贝构造
   auto_ptr(auto_ptr& __a) __STL_NOTHROW 
   //ap2的_M_ptr 被赋值为 ap调用release函数的返回值
   : _M_ptr(__a.release()) 
   {}

    //ap调用release函数
   _Tp* release() __STL_NOTHROW 
   {
     //用局部的指针__tmp接管ap的指针所指向的资源
    _Tp* __tmp = _M_ptr;
    _M_ptr = nullptr; //将ap底层的指针设为空指针
    return __tmp;//返回的就是原本ap管理的资源的地址
  }
    
private:
  _Tp* _M_ptr;
};
    auto_ptr<int> ap2(ap);
    cout << "*ap2:" << *ap2 << endl; //ok
    cout << "*ap:" << *ap << endl;  //error,因为底层是移动构造

该拷贝出现隐患被丢弃

unique_ptr(重要)

1. 独享所有权的指针 不允许进行复制或者赋值(对象语义),但是具有移动语义

2. 作为容器元素

当作为容器的元素传入的时候如果是直接传入一个左值的话,会调用复制运算符函数但是函数已经被删除,但是移动复制没有删除,所以说使用移动复制函数移交管理权。

就是使用匿名对象(move函数但是实际上的所有权被移交了,所以原来的指针失效,不用)

vector<unique_ptr<Point>> vec;
    unique_ptr<Point> up4(new Point(10,20));
    //up4是一个左值
    //将up4这个对象作为参数传给了push_back函数,会调用拷贝构造
    //但是unique_ptr的拷贝构造已经删除了
    //所以这样写会报错
    vec.push_back(up4);  //error
    
    vec.push_back(std::move(up4));  //ok,但是不用还是移交了管理权!
    vec.push_back(unique_ptr<Point>(new Point(1,3))); //ok

share_ptr(重要)

背景需求: unique_ptr是独占所有权的智能指针,有时候需要共享,有了share_ptr

原理:类似cowstring的原理,就是有一个引用计数

特征:

1. 共享所有权(可以复制或者赋值),但是具有移动语义(有移动构造和移动复制)

2. 可以作为容器元素

如果是把左值属性的指针传进去,会出现复制操作。use_count会加1

但是会出现循环引用的问题

class Child;

class Parent
{
public:
	Parent()
	{ cout << "Parent()" << endl; }
	~Parent()
	{ cout << "~Parent()" << endl; }
	//只需要Child类型的指针,不需要类的完整定义
	shared_ptr<Child> _spChild;
};

class Child
{
public:
	Child()
	{ cout << "child()" << endl; }
	~Child()
	{ cout << "~child()" << endl; }
	shared_ptr<Parent> _spParent;
};
shared_ptr<Parent> parentPtr(new Parent());
shared_ptr<Child> childPtr(new Child());
//获取到的引用计数都是1
cout << "parentPtr.use_count():" << parentPtr.use_count() << endl;
cout << "childPtr.use_count():" << childPtr.use_count() << endl;

parentPtr->_spChild = childPtr;
childPtr->spParent = parentPtr;
//获取到的引用计数都是2
cout << "parentPtr.use_count():" << parentPtr.use_count() << endl;
cout << "childPtr.use_count():" << childPtr.use_count() << endl;

当栈上的指针删除的时候还会有引用计数都是1 

解决这个问题引入弱引用指针。,因为上述的环在有栈上的指针的时候只要是有一个引用计数不为2就可以解除这个环。因为当栈上指针不再指向的时候就会变成0,销毁后另外一个引用也变为0

week_ptr弱引用的指针

不会增加引用计数

week_ptr是一个弱引用的指针,不会增加引用计数。

对于上述问题的解决办法:将Parent类中的shared_ptr类型指针换成weak_ptr

week_ptr指向不会增加引用计数

可能出现的问题就是引用计数 减到0 _wpChild指针还指向这个位置,会不会出现访问错误问题

实际上week_ptr这个指针不能访问,因为是弱引用指针不能访问

weak_ptr知道所托管的对象是否还存活,如果存活,必须要提升为shared_ptr才能对资源进行访问,不能直接访问。

use_count()只能访问share_ptr的指针指向的个数,也就是知道week_ptr有没有指向空间

week_ptr的初始化的方式

weak_ptr<int> wp;//无参的方式创建weak_ptr

//也可以利用shared_ptr创建weak_ptr 
weak_ptr<int> wp2(sp);

可能不成功当没有空间可以管理的时候,否则有另外一个shared_ptr指针指向。

shared_ptr<int> sp2 = wp.lock();
if(sp2){
cout << "创建shared成功" << endl;
cout << *sp2 << endl;
}else{
cout << "创建shared失败,托管的空间已经被销毁或者没有资源可以管理" << endl;
}

expired函数

bool flag = wp.expired();
if(flag){
cout << "托管的空间已经被销毁" << endl;
}else{
cout << "托管的空间还在" << endl;
}

删除器

unique指针的(模板参数)

背景需求: 默认的删除器使用的是delete,不能回收fopen打开的文件,因为是delete而不能是fclose。

问题描述:如果是使用原本的删除器继续使用delete,不close写的内容可能不能刷新缓冲区,如果再手动close会导致double free问题,因为delete底层本身就是free函数。

所以说需要自定义删除器将删除器中的处理设置为fclose。

struct FILECloser{
void operator()(FILE * fp){
  if(fp){
      fclose(fp);
      cout << "fclose(fp)" << endl;
  }
}
};
void test1(){
string msg = "hello,world\n";
unique_ptr<FILE,FILECloser> up(fopen("res2.txt","a+"));
//get函数可以从智能指针中获取到裸指针
fwrite(msg.c_str(),1,msg.size(),up.get());
}

shared_ptr(作为构造函数参数)

智能指针误用

将一个资源(原生指针)交给两个指针指针管理

void test0(){
//需要人为注意避免
Point * pt = new Point(1,2);
unique_ptr<Point> up(pt);
unique_ptr<Point> up2(pt);//error,出现double free
}

//unique是独占的因此不能共同管理一个对象。

void test2(){
Point * pt = new Point(10,20);
shared_ptr<Point> sp(pt);
shared_ptr<Point> sp2(pt);//error
}

//虽然说shared_ptr是可以共享的,但是也必须是使用赋值和复制的行为才是可以的,否则不可以

 这个地方addPoint如果是放回的是Point*就会出现sp3和sp管理一个Point*原生指针,

如果是返回的是shared_ptr<Point>(this)这种情况也是sp和匿名对象共用this这个point对象指针

这个地方就是用到动态内存管理中的辅助类(enable_shared_from_this)中的成员函数(shared_from_this)来实现功能。就是使用继承的方式在该类中可以使用类的成员函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1847392.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI赋能前端:你的Chrome 控制台需要AI(爱)

像会永生那样去学习,像明天就要死亡那样去生活。——圣雄甘地 大家好,我是柒八九。一个专注于前端开发技术/Rust及AI应用知识分享的Coder 此篇文章所涉及到的技术有 AI(Gemini)ChromeDevTool🪜魔法接码平台因为,行文字数所限,有些概念可能会一带而过亦或者提供对应的学习…

刘亦菲新剧玫瑰的故事

刘亦菲新剧《玫瑰的故事》&#xff1a;开放结局&#xff0c;无限遐想 当刘亦菲再次踏入荧屏&#xff0c;与导演汪俊携手打造的《玫瑰的故事》便引发了无数观众的期待与关注。这部剧不仅汇聚了众多实力派演员&#xff0c;更以其独特的剧情和精致的制作成为了近期热门的话题。《…

生成模型的两大代表:VAE和GAN

生成模型 给定数据集&#xff0c;希望生成模型产生与训练集同分布的新样本。对于训练数据服从\(p_{data}(x)\)&#xff1b;对于产生样本服从\(p_{model}(x)\)。希望学到一个模型\(p_{model}(x)\)与\(p_{data}(x)\)尽可能接近。 这也是无监督学习中的一个核心问题——密度估计…

服装连锁实体店如何做好会员营销管理

在现代商业环境中&#xff0c;会员营销管理已经成为服装连锁实体店提升客户忠诚度和增加销售额的重要手段。随着消费者对个性化服务需求的不断增加&#xff0c;如何通过有效的会员营销管理来满足客户需求、提高客户满意度和增强品牌粘性&#xff0c;已经成为服装连锁实体店亟需…

FFmpeg源码:AV_RB32宏定义分析

一、AV_RB32宏定义的作用 AV_RB32是FFmpeg源码中经常出现的一个宏&#xff0c;其定义如下&#xff1a; #ifndef AV_RB32 # define AV_RB32(p) AV_RB(32, p) #endif 该宏定义有多层。把它简化为函数&#xff0c;其函数声明可以等价于&#xff1a; uint32_t AV_RB32(uint…

网络爬虫设置代理服务器

目录 1&#xff0e;获取代理 IP 2&#xff0e;设置代理 IP 3. 检测代理 IP 的有效性 4. 处理异常 如果希望在网络爬虫程序中使用代理服务器&#xff0c;就需要为网络爬虫程序设置代理服务器。 设置代理服务器一般分为获取代理 IP 、设置代理 IP 两步。接下来&#xff0c;分…

oracle报错ora-01691,如何扩大表空间大小

1.目的 解决oracle数据库表结构空间不足&#xff0c;导致客户端或服务端程序无法连接数据库&#xff1b;知悉 oralce提示ora-01691的查询分析语句及快速解决办法 2.解决办法 查询表结构容量空间以及扩大容量后进行查询验证 select b.file_id,b.tablespace_name,b.file_name,b…

智汇云舟成为中煤集团中煤智能创新联盟成员单位

6月21日&#xff0c;第八届世界智能产业博览会平行会议暨中煤智能创新联盟交流会在天津水游城丽筠酒店顺利举行。智汇云舟受邀参与&#xff0c;并由中国中煤能源集团授予荣誉证书&#xff0c;正式成为中煤智能创新联盟成员单位。会议上&#xff0c;清华大学、中国矿业大学&…

CAC 2.0融合智谱AI大模型,邮件安全新升级

在数字化时代&#xff0c;电子邮件的安全问题日益成为关注的焦点。Coremail CACTER邮件安全人工智能实验室&#xff08;以下简称“CACTER AI实验室”&#xff09;凭借其在邮件安全领域的深入研究与创新实践&#xff0c;不断推动技术进步。 此前&#xff0c;CACTER AI实验室已获…

揭秘Xinstall如何助力App推广,提升用户量与转化率双指标!

在移动互联网时代&#xff0c;App的推广与运营成为了每个开发者必须面对的重要课题。然而&#xff0c;推广效果的评估和优化往往令众多开发者头疼不已。今天&#xff0c;我们将为您揭秘一款能够解决这一痛点的利器——Xinstall&#xff0c;带您一起探讨它如何助力App推广&#…

基于单电阻采样的电流重构

1. 单电阻采样电流重构原理 图1(a)所示是电压型三相逆变器,定义三相开 关信号为 Sa 、Sb 、Sc 。当 Sa = 1 表示A相上桥臂导 通,下桥臂关断;Sa = 0 表示相反。三相逆变器采用 SVPWM调制方式控制,有8种开关工作状态,包括 6个非零电压矢量V1 ~ V6 和2个零电压矢量V0 、V7…

代码评审——Java占位符%n的处理

问题描述 在软件开发项目中&#xff0c;特别是在处理动态内容生成与呈现至前端界面的过程中&#xff0c;正确运用占位符以确保文本完整性和数据准确性显得尤为重要。不当的占位符管理不仅可能导致语法错误或逻辑混乱&#xff0c;还会引发一系列隐蔽的问题&#xff0c;这些问题…

【CT】LeetCode手撕—54. 螺旋矩阵

目录 题目1- 思路2- 实现⭐54. 螺旋矩阵——题解思路 3- ACM实现 题目 原题连接&#xff1a;92. 反转链表 II 1- 思路 模式识别&#xff1a;螺旋矩阵 ——> 用四个指针来顺时针遍历 2- 实现 ⭐54. 螺旋矩阵——题解思路 class Solution {public List<Integer> spir…

网上预约就医取号系统

摘 要 近年来&#xff0c;随着信息技术的发展和普及&#xff0c;我国医疗信息产业快速发展&#xff0c;各大医院陆续推出自己的信息系统来实现医疗服务的现代化转型。不可否认&#xff0c;对一些大型三级医院来说&#xff0c;其信息服务质量还是广泛被大众所认可的。这就更需要…

win10修改远程桌面端口号,在Windows 10中修改远程桌面端口号的步骤

在Windows 10中&#xff0c;远程桌面服务&#xff08;Remote Desktop Services, RDS&#xff09;允许用户从远程位置访问和操作计算机。默认情况下&#xff0c;远程桌面协议&#xff08;RDP&#xff09;使用端口3389进行通信。然而&#xff0c;出于安全考虑&#xff0c;管理员可…

2008-2022年 全国31省-环境污染综合指数

环境污染综合指数是一个衡量环境污染程度或环境质量等级的抽象概括数值。它能够综合反映不同环境要素的污染情况&#xff0c;例如水污染指数和大气污染指数等。环境空气质量综合指数&#xff08;Air Quality Index, AQI&#xff09;是专门用来描述城市环境空气质量状况的一个指…

数字内容“遍地开花”,AI技术如何创新“造梦”?

文 | 智能相对论 作者 | 陈泊丞 这是春晚舞台西安分会场《山河诗长安》的一幕&#xff1a;“李白”现世&#xff0c;带领观众齐颂《将进酒》&#xff0c;将中国人骨子里的豪情与浪漫演绎得淋漓尽致。 这又是浙江义乌商品市场里的另一幕&#xff1a;只会说几个英文单词的女老板…

Linux 线程的同步与互斥

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux初窥门径⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 前言 1.资源共享问题 2.进程线程间的互斥…

JavaScript知识点大总结来了-------这一篇就足够啦!!!

JavaScript基础知识 一、对象的使用 1、创建对象 这里创建对象的方法我们采用最常用的一种&#xff1a; //第一种 <script>var Person {name: "zhangsan",age: "19",weight: "140",hight: "170",print:function(){console.…

让孩子在故事中成长,寓教于乐的趣学伴绘本投影故事机Lite

在早教产品琳琅满目的今天&#xff0c;挑选一款既吸引孩子又能提供真正教育价值的设备&#xff0c;对于家长们来说是一项挑战。我最近发现一种能够通过投影幻灯片讲故事的小工具很有趣&#xff0c;小朋友很喜欢&#xff0c;这款款名为趣学伴绘本投影故事机Lite的设备&#xff0…