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…

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…

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

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

关于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;有的话可以私聊发给我啊

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;以纪念曾经努力学习奋斗的日子。本文…

C++初学(7)

7.1、字符串 字符串是存储在内存的连续字节中的一系列字符。C处理字符串的方式有两种&#xff0c;第一种是来自C语言&#xff0c;常被称为C风格字符串&#xff0c;另一种则是基于string类库的方法。 存储在连续字节中的一系列字符意味着可以将字符存储在char数组中&#xff0…

微信小程序开发 快速学习 这篇就够了

目录 一、配置篇 &#xff08;1&#xff09;官网链接&#xff1a; &#xff08;2&#xff09;项目分析 &#xff08;3&#xff09;调试器 &#xff08;4&#xff09;预览体验 &#xff08;5&#xff09;配置文件 &#xff08;6&#xff09;配置pages &#xff08;7&…

CSRF Token 原理

CSRF 攻击 CSRF 攻击成功的关键是&#xff0c;恶意网站让浏览器自动发起一个请求&#xff0c;这个请求会自动携带 cookie &#xff0c;正常网站拿到 cookie 后会认为这是正常用户&#xff0c;就允许请求。 防范 如果在请求中加一个字段&#xff08;CSRF Token&#xff09;&am…

鸿蒙开发—黑马云音乐之Music页面

目录 1.外层容器效果 2.信息区-发光效果 3.信息区-内容布局 4.播放列表布局 5.播放列表动态化 6.模拟器运行并配置权限 效果&#xff1a; 1.外层容器效果 Entry Component export struct MuiscPage {build() {Column() {// 信息区域Column() {}.width(100%)// .backgroun…

kubernetes管理GUI工具Lens

从github上可以知道&#xff0c;lens的前端是用electron做的客户端工具&#xff0c;打开安装路径你会发现kubectl.exe,没错&#xff0c;就是你经常用的kubectl命令行的客户端工具。kubectl本来就能输出json的数据类型&#xff0c;集成前端更方便了。看到这里你是不是发现&#…

Linux云计算 |【第二阶段】AUTOMATION-DAY5

主要内容&#xff1a; YAML语法格式&#xff0c;层级关系、Ansible Playbook文件及语法格式、Ansible变量&#xff08;定义变量方法、优先级顺序、setup和debug查看变量&#xff09; 补充&#xff1a;Ansible ad-hoc 可以通过命令行形式远程管理其他主机&#xff0c;适合执行一…

视频逐帧播放查看神器-android闪黑闪白等分析辅助工具

背景 刚好有学员朋友在群里问道有没有什么播放软件可以实现对视频的逐帧即一帧一帧播放。在做android系统开发时候经常会偶尔遇到有时候是闪黑&#xff0c;闪白等一瞬间现象的问题。这类问题要分析的话就不得不需要对设备录屏&#xff0c;然后对录屏进行逐帧播放查看现象&…

2020真题-架构师案例(五)

问题1&#xff08;13分&#xff09; 针对该系统的功能&#xff0c;孪工建议采用管道-过滤器&#xff08;pipe and filter&#xff09;的架构风格&#xff0c;而王工则建议采用仓库&#xff08;reposilory&#xff09;架构风格。满指出该系统更适合采用哪种架构风格&#xff0c…