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; //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.mfbz.cn/a/730529.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java面试题:mysql执行速度慢的原因和优化

Sql语句执行速度慢 原因 聚合查询 多表查询 表数据量过大查询 深度分页查询 分析 sql的执行计划 可以使用EXPLAIN或者DESC获取Mysql如何执行SELECT语句的信息 直接在select语句前加关键字explain/desc 得到一个执行信息表 信息字段分析 possible_keys:可能使用到的索…

5G如何推动工业数字化转型?

据中国信息通信研究院测算&#xff0c;5G商用五年来&#xff0c;直接带动经济总产出约5.6万亿元&#xff0c;间接带动总产出约14万亿元&#xff0c;有力促进了经济社会高质量发展。而工业数字化转型&#xff0c;作为应对市场变革的关键战略&#xff0c;也借助5G技术卓越的高带宽…

elementui组件库实现电影选座面板demo

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Cinema Seat Selection</title><!-- 引入E…

【Nodejs 日志库 】

总结了几个比较好用的Nodejs日志库&#xff0c;我认为一个 合格的日志库 需要 支持多种传输&#xff0c;如文件、控制台、HTTP 等。可定制的日志级别和格式。异步日志记录。 根据上述的需求&#xff0c;挑选出 几款比较好用的日志库&#xff0c; 1. Winston&#xff08;Gith…

【面试题分享】重现 string.h 库常用的函数

文章目录 【面试题分享】重现 string.h 库常用的函数一、字符串复制1. strcpy&#xff08;复制字符串直到遇到 null 终止符&#xff09;2. strncpy&#xff08;复制固定长度的字符串&#xff09; 二、字符串连接1. strcat&#xff08;将一个字符串连接到另一个字符串的末尾&…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 5G基站光纤连接问题(200分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

光大证券-放量恰是入市时:成交量择时初探

核心算法 1. 在熊市中&#xff0c;各成交量时序排名出现的频次基本随排名变小而单调增大&#xff1b;在牛市中&#xff0c;各成交量时序排名出现的频次基本随排名变小而单调减少&#xff1b;而在震荡市中&#xff0c;各成交量时序排名出现的频次两头大&#xff0c;中间小&…

天津这场智博会,成了智能时代的风向标

毫无疑问&#xff0c;这是一场智能产业的盛宴。 2024年6月20日至23日&#xff0c;国家会展中心&#xff08;天津&#xff09;迎来了一场智能科技领域的盛会——世界智能产业博览会&#xff1a;这场以“智行天下、能动未来”为主题的博览会&#xff0c;汇聚了全球49个国家和地区…

域内攻击手法——域内用户枚举和密码喷洒

一、域内用户枚举 1、域内用户枚举原理 域内用户枚举可以在无域内有效凭据的情况下&#xff0c;枚举出域内存在的用户名&#xff0c;并对其进行密码喷洒攻击&#xff0c;以此获得域内的有效凭据&#xff0c;在 Kerberos 协议认证的 AS-REQ 阶段&#xff0c;客户端向 AS 发送的…

MySQL之优化服务器设置(一)

优化服务器设置 配置MySQL的IO行为 有一些配置影响着MySQL怎样同步数据到磁盘以及如何做恢复操作。这些操作对性能的影响非常大&#xff0c;因为都涉及到昂贵的IO操作。它们也表现了性能和数据安全之间的权衡。通常&#xff0c;保证数据立刻并且一致地写到磁盘是很昂贵的。如…

【文心智能体大赛】迎接属于你的休闲娱乐导师!

迎接属于你的休闲娱乐导师&#xff01; 前言创建智能体发布智能体最后结语 前言 文心智能体平台AgentBuilder 是百度推出的基于文心大模型的智能体&#xff08;Agent&#xff09;平台&#xff0c;支持广大开发者根据自身行业领域、应用场景&#xff0c;选取不同类型的开发方式&…

AI全栈之logo生成:执文,描摹,妙哉~

前言 前几日体验了国产的AI-Agents产品coze 它是一种能够自主执行任务、与环境进行交互并根据所获取的信息做出决策和采取行动的软件程序 并且可以自己去创建属于自己的AIBot&#xff0c;还是很有意思的&#xff0c;大家可以去体验体验 在体验过程中&#xff0c;我发现在创…

肾虚学习实验第T1周:实现mnist手写数字识别

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/)** 目录 一、前言 作为一名研究牲&#xff0…

数据库复习——模式分解

模式分解这边主要包括无损分解和保持函数依赖的分解两种形式&#xff0c;简单整理一下。 无损分解 把一个 R R R 分成 ρ { R 1 , R 2 , ⋯ , R k } \rho \{R_1,R_2,\cdots,R_k\} ρ{R1​,R2​,⋯,Rk​}&#xff0c;然后通过自然连接 R 1 ⋈ R 2 ⋈ ⋯ ⋈ R k R_1\bowtie R…

可视化数据科学平台在信贷领域应用系列七:自动机器学习(下篇)

在当今金融科技迅速发展的时代&#xff0c;自动机器学习&#xff08;AutoML&#xff09;逐步成为了信贷风控领域的重要工具。随着大数据和人工智能技术的进步以及信贷风险环境的快速变化&#xff0c;传统人工建模模式的时效性已经难以应对复杂多变的挑战。自动机器学习框架将数…

AI创作音乐引发的深思

在最近一个月中&#xff0c;音乐大模型的迅速崛起让素人生产音乐的门槛降到了最低。这一变革引发了关于AI能否彻底颠覆音乐行业的广泛讨论。在初期的兴奋过后&#xff0c;人们开始更加理性地审视AI在音乐领域的应用&#xff0c;从版权归属、原创性、创作质量、道德层面以及法律…

【linux】dup文件描述符复制函数和管道详解

目录 一、文件描述符复制 1、dup函数&#xff08;复制文件描述符&#xff09; ​编辑 2、dup2函数&#xff08;复制文件描述符&#xff09; ​编辑 二、无名管道pipe 1、概述 2、无名管道的创建 3、无名管道读写的特点 4、无名管道ps -A | grep bash实现 三、有名管道FI…

没有超头、最低价的视频号618战况如何?有何趋势变化?| 视频号618观察

转眼618大促已接近尾声&#xff0c;今年的你有剁手哪些好物吗&#xff1f;对618的整体感觉又是如何呢&#xff1f; 这是12年来&#xff0c;第一个电商平台没有预售付定金的618&#xff0c;当然或许此后的双11、每一次大促也将逐渐回归传统&#xff0c;回归本质。 而对于视频号来…

普通变频器位置闭环控制(S7-1200PLC工艺对象模拟量轴)

1、S7-1200PLC控制V90总线伺服通过工艺对象实现定位控制 S7-1200PLC和V90总线伺服通过工艺对象实现定位控制(标准报文3应用)_1200报文3控制v90-CSDN博客文章浏览阅读182次。V90伺服驱动器调试软件SINAMICS V-ASSISTANT Commissioning tool下载地址如下:西门子官网选型|资料CS…

linux下的进程通讯

一. 实验内容 1&#xff0e;编写一个程序&#xff0c;实现在两个进程之间运用管道进行通讯。程序中创建一个子进程&#xff0c;然后父、子进程各自独立运行。父进程不断地在标准输入设备上读入小写字母&#xff0c;写入管道。子进程不断地从管道中读取字符&#xff0c;转换为大…