6.21 移动语义与智能指针

    //先构造,再拷贝构造//利用"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; //okcout << "*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);  //errorvec.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.mzph.cn/news/857054.shtml

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

相关文章

squareline studio浅尝(1)在对话框添加键盘

因项目需要&#xff0c;需要修改IP地址等参数&#xff0c;需要编辑文本对话框内容&#xff0c;这时候就需要调用键盘&#xff0c;操作如下。主要为了做笔记。如有误导请及时留言。 1&#xff09;拖一个键盘到对话框页面。默认把它隐藏&#xff08;flag:hidden&#xff09; 2&…

想更好应对突发网络与业务问题?您需要一款“全流量”

全流量分析&#xff0c;能为我做什么&#xff1f; 在生活中遇到问题&#xff0c;我们的第一反应可能是拿出手机拍照记录&#xff0c;方便后续处理。这些问题是临时的、突发的。 流量分析&#xff0c;就是网络中的“手机”&#xff0c;针对突发的网络故障和安全事件&#xff0…

课程管理系统

摘 要 在大学里&#xff0c;课程管理是一件非常重要的工作&#xff0c;教学工作人员每天都要与海量的数据和信息打交道。确保数据的精确度和完整程度&#xff0c;影响着每一位同学的学习、生活和各种活动的正常展开&#xff0c;更合理的信息管理也为高校工作的正规化运行和规范…

解锁空间数据奥秘:ArcGIS Pro与Python双剑合璧,处理表格数据、矢量数据、栅格数据、点云数据、GPS数据、多维数据以及遥感云平台数据等

ArcGISPro提供了用户友好的图形界面&#xff0c;适合初学者快速上手进行数据处理和分析。它拥有丰富的工具和功能&#xff0c;支持各种数据格式的处理和分析&#xff0c;适用于各种规模的数据处理任务。ArcGISPro在地理信息系统&#xff08;GIS&#xff09;领域拥有广泛的应用&…

安全生产第一位,靠谱的漏油监测系统有哪些?

漏油监测系统&#xff0c;一般是由漏油绳、漏油控制器、监控云平台组成&#xff0c;用于实时检测油库、油罐、加油站、输油管道、油类化工厂等场所是否发生漏油事故。在这些地方一旦发生漏油&#xff0c;就极可能引发爆炸&#xff0c;损害到人员及财产安全。而一套靠谱的漏油监…

mysql 主从延迟

mysql 主从延迟 精华推荐 | 【MySQL技术专题】「主从同步架构」全面详细透析MySQL的三种主从复制&#xff08;Replication&#xff09;机制的原理和实战开发&#xff08;原理实战&#xff09; https://blog.csdn.net/l569590478/article/details/128329929 mysql主从之多线程复…

iptables(6)扩展匹配条件--tcp-flags、icmp

简介 前面我们已经介绍了不少的扩展模块,例如multiport、iprange、string、time、connlimit模块,但是在tcp扩展模块中只介绍了tcp扩展模块中的”--sport”与--dport”选项,并没有介绍”--tcp-flags”选项,那么这篇文章,我们就来认识一下tcp扩展模块中的”--tcp-flags”和i…

【MySQL进阶之路 | 高级篇】InnoDB搜索引擎行格式

1. COMPACT行格式 COMPACT行格式是MySQL5.1的默认行格式.其结构示意图如下. 大体可以分为两部分. 记录的额外信息.这里面有包括变长字段长度列表&#xff0c;NULL值列表和记录头信息.记录的真实数据. (1).变长字段长度列表 MySQL支持一些变长的数据类型.比如VARCHAR(m), VA…

基于JSP技术的个性化影片推荐系统

开头语&#xff1a;你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;JSPServlet 工具&#xff1a;MyEclipse、Tomcat、MySQL 系统展示 首页 …

langchain教程-(1)Prompt模板

LangChain 的核心组件 模型 I/O 封装 LLMs&#xff1a;大语言模型Chat Models&#xff1a;一般基于 LLMs&#xff0c;但按对话结构重新封装PromptTemple&#xff1a;提示词模板OutputParser&#xff1a;解析输出 数据连接封装 Document Loaders&#xff1a;各种格式文件的加载…

Docker Desktop进入界面时一直转圈的解决办法记录

我的win10版本如下&#xff0c;是支持安装的&#xff0c;不支持安装的&#xff0c;可以先升级系统版本&#xff1a; 起初是因为运行Docker Desktop时一直转圈&#xff0c;无法进入主面板&#xff0c;百度之&#xff0c;需要安装hype-v环境&#xff0c;找到以下 勾选Hyper-V下的…

分享由AI制定一个商城网站的开发计划及推荐的开发语言

商城网站开发计划 一、项目概述 本商城网站开发计划旨在创建一个功能齐全、用户友好的在线购物平台&#xff0c;为顾客提供商品浏览、搜索、购物车管理、订单跟踪、在线支付等服务。商城将支持多种商品分类&#xff0c;包括但不限于电子产品、家居用品、服饰鞋帽等。 二、开…

在小公司可以做大模型吗?心得经验分享_第一份工作在小公司做大模型好吗

导读 继ChatGPT发布以来&#xff0c;各种大模型相继问世。近日Sora也突然走入大众的视野。那么做模型是否只有OpenAI这种巨头公司才能做呢&#xff0c;答案是否定的。在小公司做大模型&#xff0c;是可以的。本文作者结合切身经历&#xff0c;回答了如何在小公司做大模型。 在…

【Linux】进程信号2——阻塞信号,捕捉信号

1.阻塞信号 1.1. 信号其他相关常见概念 在开始内容之前&#xff0c;先介绍一些信号的专业名词&#xff1a; 实际执行信号的处理动作称为信号递达&#xff08;Delivery&#xff09;信号从产生到递达之间的状态&#xff0c;称为信号未决&#xff08;Pending&#xff09;&#…

Log4j2异步打印可变对象的问题

现象 应用代码如下&#xff1a; Test test new Test();test.setA(1);test.setB("1");log.info("before modification: {} \t ",test);test.setA(2);test.setB("2");log.info("after modification: {} \t ",test);问题应用的日志控制…

组装盒示范程序

代码; #include <gtk-2.0/gtk/gtk.h> #include <glib-2.0/glib.h> #include <stdio.h>int main(int argc, char *argv[]) {gtk_init(&argc, &argv);GtkWidget *window;window gtk_window_new(GTK_WINDOW_TOPLEVEL);gtk_window_set_title(GTK_WINDO…

用进程和线程完成TCP进行通信操作及广播和组播的通信

进程 代码 #include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>#include <unistd.h>#include <stdlib.h>#include <signal.h>#includ…

1958springboot VUE宿舍管理系统开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot VUE宿舍管理系统是一套完善的完整信息管理类型系统&#xff0c;结合springboot框架和VUE完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09; &#xff0c;系统具有完整的源代码和数…

PyTorch实战:模型训练中的特征图可视化技巧

1.特征图可视化&#xff0c;这种方法是最简单&#xff0c;输入一张照片&#xff0c;然后把网络中间某层的输出的特征图按通道作为图片进行可视化展示即可。 2.特征图可视化代码如下&#xff1a; def featuremap_visual(feature, out_dirNone, # 特征图保存路径文件save_feat…

O2OA的数据库数据库配置-使用不同用户访问Oracle时报错-表或视图不存在

在使用Oracle数据库时&#xff0c;多个O2OA服务器同一个Oracle实例中使用不同的用户启动时&#xff0c;可能会遇到数据库访问的错误。本篇阐述此类问题以及解决方案。 一、先决条件&#xff1a; 1、O2OA已经下载并且解压到指定的目录&#xff1b; 2、Oracle数据库已经完成安…