C++11中的右值引用以及移动构造等

目录

一、右值引用

1.左值引用和右值引用

2.左值引用与右值引用比较

3.右值引用使用场景和意义

1️⃣ 传返回值

2️⃣ STL中的应用

4.完美转发

模板中的&& 万能引用(引用折叠)

二、 新的类功能

1.默认成员函数

2.类成员变量初始化

3.强制生成默认函数的关键字default:

4.禁止生成默认函数的关键字delete:

5.继承和多态中的final与override关键字


一、右值引用

1.左值引用和右值引用

传统的C++语法中就有引用的语法,而C++11中新增了的右值引用语法特性,所以从现在开始我们之前学习的引用就叫做左值引用。无论左值引用还是右值引用,都是给对象取别名

什么是左值?什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

什么是右值?什么是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址。右值引用就是对右值的引用,给右值取别名。

int main()
{// 以下的p、b、c、*p都是左值// 左值:可以取地址int* p = new int(0);int b = 1;const int c = b;*p = 10;string s("111111");s[0];cout << &c << endl;cout << &s[0] << endl;//    // 右值:不能取地址
//    double x = 1.1, y = 2.2;
//    // 以下几个都是常见的右值,常量临时对象,匿名对象
//    10;
//    x + y;
//    fmin(x, y);
//    string("11111");
//
//    cout << &10 << endl;
//    cout << &(x+y) << endl;
//    cout << &(fmin(x, y)) << endl;
//    cout << &string("11111") << endl;return 0;
}

需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址,也就是说例如:不能取字面量10的地址,但是rr1引用后,可以对rr1取地址,也可以修改rr1。如果不想rr1被修改,可以用const int&& rr1 去引用,是不是感觉很神奇,这个了解一下实际中右值引用的使用场景并不在于此,这个特性也不重要。

int main()
{double x = 1.1, y = 2.2;int&& rr1 = 10;const double&& rr2 = x + y;rr1 = 20;rr2 = 5.5; // 报错return 0;
}

注:无论是左值引用还是右值引用,在字面上不占空间,在汇编、底层上是开空间的

2.左值引用与右值引用比较

左值引用总结:

✸ 左值引用只能引用左值,不能引用右值。

✸ 但是const左值引用既可引用左值,也可引用右值

int main(){// 左值引用只能引用左值,不能引用右值int a = 10;int& ra1 = a;// ra为a的别名//int& ra2 = 10; 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}

右值引用总结:

✸ 右值引用只能右值,不能引用左值。

✸ 但是右值引用可以move以后的左值。

✸ move的本质是强制类型转换

int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;
//    int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);string s("111111");//s是左值 “111111”是右值string&& rrx4 = move(s);//左值move后给右值引用// move的本质是强制类型转换string&& rrx5 = (string&&)s;//与上句等同return 0;
}

 

3.右值引用使用场景和意义

1️⃣ 传返回值

前面我们可以看到左值引用既可以引用左值和又可以引用右值,那为什么C++11还要提出右值引用呢?是不是化蛇添足呢?下面我们来看看左值引用的短板,右值引用是如何补齐这个短板的!

//构造函数
string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}
// 赋值重载string& operator=(const string& s){cout << "string& operator=(const string& s) -- 深拷贝" << endl;if (this != &s){_str[0] = '\0';_size = 0;reserve(s._capacity);//开空间,深拷贝for (auto ch : s){push_back(ch);}}return *this;}bit::string to_string(int value)
{bool flag = true;if (value < 0){flag = false;value = 0 - value;}bit::string str;//临时对象while (value > 0){int x = value % 10;value /= 10;str += ('0' + x);}if (flag == false){str += '-';}std::reverse(str.begin(), str.end());return str;
}
}int main()
{bit::string s1;s1 = bit::to_string(1234);return 0;
}输出结果:
string(char* str)		//构造 bit::string s1;
string(char* str)		//构造内部str对象
string& operator=(const string& s) -- 深拷贝  //赋值重载

 对于返回值,编译器会生成临时对象,再对临时对象进行拷贝构造,对于临时对象进行拷贝构造这不就浪费了吗?为了更进一步优化,C++11新引入移动构造,以及移动赋值

    // 移动构造// 临时创建的对象,不能取地址,用完就要消亡// 深拷贝的类,移动构造才有意义string(string&& s){cout << "string(string&& s) -- 移动拷贝" << endl;swap(s);}
    // 移动赋值string& operator=(string&& s){cout << "string& operator=(string&& s) -- 移动拷贝" << endl;swap(s);//转移资源return *this;}
int main()
{bit::string s1;s1 = bit::to_string(1234);return 0;
}
输出结果:
string(char* str)
string(char* str)
string& operator=(string&& s) -- 移动拷贝

我们先看编译器做的几种优化

情况一:string 没有移动构造,只有拷贝构造也就是C++11之前的

优化前:

✸ 传值返回会生成临时对象

✸ 传值返回会导致至少1次拷贝构造(如果是一些旧一点的编译器可能是两次拷贝构造)。

优化后

✸ 编译器认为两次拷贝太浪费了,直接优化临时对象,发生一次拷贝


情况二:string有拷贝构造,也有移动构造C++11之后 

优化前: ✸ str是一个左值(可以取地址)

✸ 临时对象是一个右值

✸ 把一次拷贝构造,变为移动构造

优化后:

✸ 没有临时对象

✸ 理应来说str是一个左值,应进行拷贝构造,但是这里把str隐式转换为右值,直接进行移动构造

✸ 相当于传值返回,没有拷贝了

更激进的优化

 

int main()
{bit::string s1 = bit::to_string(1234);return 0;
}
输出结果:
string(char* str) 直接构造,没有移动

ps:这两种情况是不一样的

//编译器再厉害也不行,这种情况还是要靠移动赋值与移动构造
int main()
{bit::string s1;s1 = bit::to_string(1234);return 0;
}int main()
{bit::string s1 = bit::to_string(1234);//   s1 = bit::to_string(1234);return 0;
}

我们之前使用的一直都是C++11/C++11以后的编译器,所以我们以前也并未提起相关知识,而是直接用,但我们现在学习了之后,发现不影响使用,该随便用就随便用,本次也只是介绍如果没有C++11,而是以前的,我们对于这种情况就很麻烦处理

2️⃣ STL中的应用

对于库中list 

 

int main()
{cout << "-------1-------" << endl;list<bit::string> lt;cout << "-------2-------" << endl;bit::string s1("111111111111111111111");cout << "-------3-------" << endl;lt.push_back(s1);cout << "-------4-------" << endl;lt.push_back(bit::string("22222222222222222222222222222"));cout << "-------5-------" << endl;lt.push_back("3333333333333333333333333333");cout << "-------6-------" << endl;lt.push_back(move(s1));return 0;
}输出结果:
-------1-------
-------2-------
string(char* str)
-------3-------
string(const string& s) -- 深拷贝
-------4-------
string(char* str)
string(string&& s) -- 移动拷贝
-------5-------
string(char* str)
string(string&& s) -- 移动拷贝
-------6-------
string(string&& s) -- 移动拷贝

对于我们之前手搓链表我们加上移动版本push_back 和 insert

template<class T>
struct ListNode
{ListNode<T>* _next;ListNode<T>* _prev;T _data;ListNode(const T& data = T()):_next(nullptr),_prev(nullptr),_data(data){}ListNode(T&& data):_next(nullptr), _prev(nullptr), _data(move(data)){}
};void push_back(const T& x){insert(end(), x);}void push_back(T&& x){insert(end(), move(x));}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* newnode = new Node(x);Node* prev = cur->_prev;// prev  newnode  curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}iterator insert(iterator pos, T&& x){Node* cur = pos._node;Node* newnode = new Node(move(x));Node* prev = cur->_prev;// prev  newnode  curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}

 

int main()
{cout << "-------1-------" << endl;bit::list<bit::string> lt;//这里会由于创造哨兵位头节点 发生构造+拷贝构造cout << "-------2-------" << endl;//左值bit::string s1("111111111111111111111");cout << "-------3-------" << endl;//左值lt.push_back(s1);cout << "-------4-------" << endl;//右值lt.push_back(bit::string("22222222222222222222222222222"));cout << "-------5-------" << endl;//右值 产生临时对象lt.push_back("3333333333333333333333333333");//右值cout << "-------6-------" << endl;lt.push_back(move(s1));cout << "-------7-------" << endl;bit::string&& r1 = bit::string("22222222222222222222222222222");// r1(右值引用本身)的属性是左值还是右值?-> 左值return 0;
}输出结果:
-------1-------
string(char* str)
string(const string& s) -- 深拷贝
-------2-------
string(char* str)
-------3-------
string(const string& s) -- 深拷贝
-------4-------
string(char* str)
string(string&& s) -- 移动拷贝
-------5-------
string(char* str)
string(string&& s) -- 移动拷贝
-------6-------
string(string&& s) -- 移动拷贝
-------7-------
string(char* str)

 

注意:由于move以后本身的属性还是左值,所以只要使用到右值,我们都需要写一个右值版本,少一个都不行,比如这里的节点构造、push_back、insert

编译器是能过自动匹配右值还是左值

void func(const bit::string& s)
{cout << "void func(bit::string& s)" << endl;
}void func(bit::string&& s)
{cout << "void func(bit::string&& s)" << endl;
}int main()
{cout << "左值" << endl;bit::string s1("1111111");func(s1);cout << "右值" << endl;func((bit::string&&)s1);cout << "右值" << endl;func(bit::string("1111111"));//强转成左值// func((bit::string&)bit::string("1111111"));return 0;
}
输出结果
左值
string(char* str)
void func(bit::string& s)
右值
void func(bit::string&& s)
右值
string(char* str)
void func(bit::string&& s)

4.完美转发

比如我们写一个Fun()函数,我们既要写它的左值版本,又要写它的右值版本,这是不是不太合适?所以C++引入完美转发

模板中的&& 万能引用(引用折叠)

### ```cpp
//函数模版中
template <class T>
void Func(T&& x)
{}
```

对于void Func(T&& x)中的T&& x,如果我们进行传值

✸ 如果传的是一个右值类型,那么T&& x就是一个右值引用

✸ 如果传的是左值类型,那么T&& x就是一个左值引用

使用关键字forward可以保留原先数据属性,传什么,就保留什么属性,解决了需要传左值右值不需要写太多版本的问题

底层就是,编译器很辛苦,生成各种类型的函数(函数重载),本质也是类型转换

完美转发是一个函数模版

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }// 引用
// 传左值->左值引用
// 传右值->右值引用
template<typename T>
void PerfectForward(T&& t)
{// 模版实例化是左值引用,保持属性直接传参给Fun// 模版实例化是右值引用,右值引用属性会退化成左值,转换成右值属性再传参给Fun// 使用std::forward完美转发参数到另一个函数Fun(forward<T>(t));
}int main()
{PerfectForward(10);           // 右值int a;PerfectForward(a);            // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b);              // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}
输出结果:
右值引用
左值引用
右值引用
const 左值引用
const 右值引用

二、 新的类功能

1.默认成员函数

原来C++类中,有6个默认成员函数:

✸ 构造函数

✸ 析构函数

✸ 拷贝构造函数

✸ 拷贝赋值重载

✸ 取地址重载

✸ const 取地址重载

C++11 新增了两个:移动构造函数和移动赋值运算符重载。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

✸ 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。

✸ 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动赋值,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)

✸ 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

class Person
{
public:Person(const char* name = "111111111111", int age = 0):_name(name), _age(age){}这里我们没有写析构函数 、拷贝构造、拷贝赋值重载// 自动生成拷贝构造和移动构造private:bit::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);移动赋值测试,原理同移动构造//Person s4;// s4 = std::move(s2);return 0;
}

 

如果我们加上析构函数

    ~Person(){}输出结果:
string(char* str)
string(const string& s) -- 深拷贝
string(const string& s) -- 深拷贝

 

注:一个函数如果需要显示写析构函数,说明有资源需要释放

✸ 说明需要显示写拷贝构造和赋值重载

✸ 说明需要显示写移动构造和移动赋值

自动生成的移动构造,对于Date这样的类,其实没有什么意义,因为它和拷贝构造的功能是一样的

自动生成的移动构造,对于Person这样的类是很有意义的,因为Person是右值时,他内部的string也是右值,string就可以走移动构造,提高效率了

2.类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这个我们在类和对象默认就讲了,这里就不再细讲了。

3.强制生成默认函数的关键字default:

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成,也就是算显示生成,因此我们需要额外注意一下默认成员函数的条件

    Person(Person&& p) = default;Person& operator=(Person && p) = default;Person(const Person& p) = default;Person& operator=(const Person& p) = default;

4.禁止生成默认函数的关键字delete:

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class Person
{
public:Person(const char* name = "111111111111", int age = 0):_name(name), _age(age){}// 只声明不实现,声明为私有// C++98的做法
//private:
//    Person(const Person& p);
//    Person& operator=(const Person & p);//C++11的做法Person(const Person& p) = delete;Person& operator=(const Person& p) = delete;
private:bit::string _name;int _age;
};

5.继承和多态中的final与override关键字

这个我们在继承和多态章节已经进行了详细讲解这里就不再细讲,需要的话去复习继承和多台章节吧。

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

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

相关文章

线程池学习(一)

1.线程池有什么作用 降低资源消耗&#xff1a;通过池化技术重复利⽤已创建的线程&#xff0c;降低线程创建和销毁造成的损耗。 提⾼响应速度&#xff1a;任务到达时&#xff0c;⽆需等待线程创建即可⽴即执⾏。 提⾼线程的可管理性&#xff1a;线程是稀缺资源&#xff0c;如果…

ProxmoxPVE虚拟化平台--安装PVE虚拟机

Proxmox 虚拟机 Proxmox是一个基于Debian Linux和KVM的虚拟化平台&#xff0c;‌它提供了虚拟化的环境&#xff0c;‌允许用户在同一台物理机上运行多个虚拟机。‌Proxmox虚拟环境&#xff08;‌PVE&#xff09;‌是一个开源项目&#xff0c;‌由Proxmox Server Solutions Gmb…

【复读EffectiveC++23】条款23:宁以 non-member、non-friend替换member函数

条款23&#xff1a;宁以 non-member、non-friend替换member函数 这是C设计的一个基本原则&#xff0c;主要目的是减少面向对象设计中的耦合&#xff0c;提高软件的内聚性和可复用性。non-member、non-friend函数可以不受类内部实现的影响&#xff0c;因此更加灵活和可复用。 …

Power Tower

Problem - D - Codeforces 牛客和codeforce都有 递归处理l,r&#xff0c;终点是lr && mod1 用扩展欧拉定理 // Problem: D. Power Tower // Contest: Codeforces - Codeforces Round 454 (Div. 1, based on Technocup 2018 Elimination Round 4) // URL: https://c…

学习HTML、CSS和JavaScript的完整路线指南

第一步&#xff1a;理解基础概念 HTML基础 HTML简介和结构 什么是HTML&#xff1f;它在Web开发中的角色。HTML文档的基本结构&#xff1a;<!DOCTYPE>, <html>, <head>, <body>等标签的作用和使用方法。 常用HTML元素 文本相关&#xff1a;段落 <p&g…

【Socket 编程】应用层自定义协议与序列化

文章目录 再谈协议序列化和反序列化理解 read、write、recv、send 和 tcp 为什么支持全双工自定义协议网络计算器序列化和反序列化 再谈协议 协议就是约定&#xff0c;协议的内容就是约定好的某种结构化数据。比如&#xff0c;我们要实现一个网络版的计算器&#xff0c;客户端…

【logstash】logstash使用多个子配置文件

这里有个误区在pipelines.yml中写conf.d/*&#xff0c;实测会有问题&#xff0c;不同的filter处理逻辑会复用。 现在有两个从kafka采集日志的配置文件&#xff1a;from_kafka1.conf&#xff0c;from_kafka2.conf 修改pipelines.yml配置文件 config/pipelines.yml- pipeline.i…

关于P2P(点对点)

P2P 是一种客户端与客户端之间&#xff0c;点对点连接的技术&#xff0c;在早前的客户端都是公网IP&#xff0c;没有NAT的情况下&#xff0c;P2P是较为容易实现的。 但现在的P2P&#xff0c;实现上面会略微有一些复杂&#xff1a;需要采取UDP打洞的技术&#xff0c;但UDP打出来…

asp.net mvc 三层架构开发商城系统需要前台页面代完善

一般会后端开发&#xff0c;都不太想写前台界面&#xff0c;这套系统做完本来想开源&#xff0c;需要前台界面&#xff0c;后台已开发&#xff0c;有需求的朋友&#xff0c;可以开发个前端界面完善一下&#xff0c;有的话可以私聊发给我啊

python_使用多进程来处理数据写入Excel文件_multiprocessing.Process

python_使用多进程来处理数据写入Excel文件 优势&#xff1a;与多线程相比&#xff0c;多进程写入速度要更快&#xff0c;12万多行数据处理用时3.52秒&#xff0c;比多进程快了1秒左右。 import pandas as pd from io import BytesIO import multiprocessing import time impor…

Spring源码-AOP

1、spring aop和aspectJ什么关系&#xff1f; aop是编程思想&#xff0c;spring aop被aspectJ都是aop思想的具体实现。spring aop为了不重复造轮子&#xff0c;通过一定的取舍选取了aspectJ中适合自己的注解。spring初期版本的aop只支持通过实现aop接口的方式来实现切面增强&a…

Nginx 最常用的命令

目录 一、Nginx 安装与配置 1.1 下载与安装 1.2 配置文件 二、Nginx 基本操作 2.1 启动与停止 2.2 重启与重新加载 三、日志管理 3.1 访问日志 3.2 错误日志 四、虚拟主机管理 4.1 配置虚拟主机 4.2 管理虚拟主机 五、性能优化 5.1 缓存配置 5.2 连接优化 Nginx…

Redis(三)

1. java连接redis java提高连接redis的方式jedis. 我们需要遵循jedis协议。 引入依赖 <!--引入java连接redis的驱动--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.3.1</version&g…

Android Framework 之AMS

它管理了系统的四大组件:Activity、Service、ContentProvider、Broadcast。 它除了管理四大组件外&#xff0c;同时也负责管理和调度所有的进程 AMS相关目录结构 AMS代码主要在下面几个目录(AndroidQ上AMS相关部分功能移到了wm下)&#xff1a; frameworks/base/core/java/andro…

记录|LabVIEW从0开始

目录 前言一、表达式节点和公式节点二、脚本与公式2.1 公式 三、Excel表格3.1 位置3.2 案例&#xff1a;波形值存入Excel表中3.3 案例&#xff1a;行写入&#xff0c;列写入 四、时间格式化4.1 获取当前时间4.2 对当前时间进行格式化 更新时间 前言 参考视频&#xff1a; LabVI…

【STL】之 vector 使用方法及模拟实现

前言&#xff1a; 本文主要讲在C STL库中vector容器的使用方法和底层的模拟实现~ 成员变量的定义&#xff1a; 对于vector容器&#xff0c;我们首先采用三个成员变量去进行定义&#xff0c;分别是&#xff1a; private:iterator _start; // 指向数据块的开始iterator _finish…

React类组件生命周期与this关键字

类组件生命周期 参考链接 一图胜千言&#xff08;不常用的生命周期函数已隐藏&#xff09; 代码&#xff1a; //CC1.js import { Component } from "react";export default class CC1 extends Component {constructor(props) {super(props);console.log("con…

【Vue3】watchEffect

【Vue3】watchEffect 背景简介开发环境开发步骤及源码 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的日子。本文…

【代码随想录第37天| 完全背包,518. 零钱兑换 II ,377. 组合总和 Ⅳ,70. 爬楼梯 (进阶)】

完全背包 完全背包中&#xff0c;每件物品都有无数件&#xff1b;这主要影响了遍历背包容量时的遍历顺序&#xff0c;应该从小到大去遍历&#xff0c;这样才能包括有多件相同物品的情况。 思路 先遍历物品&#xff0c;再遍历背包 for(int i 0; i < weight.size(); i) {…

pnpm 设置国内源

pnpm config set registry https://registry.npmmirror.com/