C++11右值引用

        C++11增加了一个新的类型,称为右值引用,标记为T&&。

        左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象。

        一个区分左值和右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。所有的具名变量或对象都是左值,而右值不具名。

        C++11中,右值由两个概念构成,一个是将亡值,另一个是纯右值,比如,非引用返回的临时变量,运算表达式产生的临时变量,原始字面量和lambda表达式都是纯右值。而将亡值是C++11新增的,与右值引用相关的表达式,比如,将要被移动的对象,T&&函数返回值,std::move返回值和转换为T&&的类型的转换函数的返回值。

        C++11中所欲的值必属于左值,将亡值,纯右值三者之一,将亡值和纯右值都属于右值。区分表达式的左右值属性有一个减编方:若可对表达式用&符取址,则为左值,否则为右值。

        比如,简单的赋值语句:

    int i = 0;

        在这条语句中,i是左值,0是字面量,就是右值。在上面的代码中,i可以被引用,0就不可以了。字面量都是右值。

&&的特性

        右值引用就是对一个右值进行引用的类型。因为右值不具名,所以我们只能通过引用的方式找到它。

        无论声明左值引用还是右值引用都必须立即进行初始化,因此引用类型本身并不拥有所绑定对象的内存,只是该对象的一个别名。通过右值引用的声明,该右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样,只有该变量还活着,该右值临时量将会一直存活下去。

        看一下下面代码:

#include <iostream>
using namespace std;int g_constructCount = 0;
int g_copyconstructCount = 0;
int g_destructCount = 0;struct A
{A(){cout << "construct: " << ++g_constructCount << endl;}A(const A& a){cout << "copy construct: " << ++g_copyconstructCount << endl;}~A(){cout << "destruct: " << ++g_destructCount << endl;}};A getA()
{return A();
}int main()
{A a = getA();return 0;
}

        为了清楚的观察临时值,在G++下编译时设置编译选项-fno-elide-constructors来关闭返回值优化效果。

        输出结果:

        ​​​​​​

        从上面的例子中可以看到,在没有返回值优化的情况下,拷贝构造函数调用了两次,一次是在getA()函数内部创建的对象返回后构造一个临时对象产生的,另一次是在main函数中构造a对象产生的。第二次的destruct是因为临时对象在构造a对象之后就销毁了。如果开启返回值优化,输出结果将是:

        

        可以看到返回值优化将会把临时对象优化掉,但这不是C++标志,是各编译器的优化规则。我们在回到之前提到的可以通过右值引用来延长临时右值的生命周期,如果在上面的代码中通过右值引用来绑定函数返回值,结果就不一样了。在编译时设置编译选项-fno-elide-constructors。

#include <iostream>
using namespace std;int g_constructCount = 0;
int g_copyconstructCount = 0;
int g_destructCount = 0;struct A
{A(){cout << "construct: " << ++g_constructCount << endl;}A(const A& a){cout << "copy construct: " << ++g_copyconstructCount << endl;}~A(){cout << "destruct: " << ++g_destructCount << endl;}};A getA()
{return A();
}int main()
{A&& a = getA();return 0;
}

        输出结果:

        

        通过右值引用,比之前少了一个拷贝构造和析构,原因在于右值引用绑定了右值,让临时右值的生命周期延长了。我们可以利用这一特点做一些优化,即避免临时对象的拷贝构造和析构。

        实际上T&&并不是一定表示右值,它绑定的类型是未定的,既可能是左值又可能是右值。

    template<typename T>void f(T&& param;)f(10);    ///10是右值int x = 10;f(x);    ///x是左值

        从上面的代码可以看出,param有时是左值,有时是右值,因为在上面的例子中有&&,这表示param实际上是一个未定义的引用类型。这个未定的引用类型称为universal references,它必须被初始化,它是左值还是右值引用取决于它的初始化,如果&&被一个左值初始化,它就是一个左值;如果它被一个右值初始化,它就是一个右值。

#include <iostream>
using namespace std;void PrintValue(int& i)
{cout << "lvalue: " << i << endl;
}void PrintValue(int&& i)
{cout << "rvalue: " << i << endl;
}void Forward(int&& i)
{PrintValue(i);
}int main()
{int i = 0;PrintValue(i);PrintValue(0);Forward(2);return 0;
}

        Forward函数接受的是一个右值,但是转发给PrintValue时又变成了左值,因为在Forward中调用PrintValue时,右值i变成了一个命名对象,编译器会将其当做左值处理。

右值引用优化

        对于含有堆内存的类,我们需要提供深拷贝的拷贝构造函数,如果使用默认构造函数会导致堆内存的重复删除,比如下面的代码:

#include <iostream>
using namespace std;class A
{
public:A():m_ptr(new int(8)){cout << "construct: " << m_ptr << endl;}~A(){cout << "m_ptr: " << m_ptr << endl;delete  m_ptr;}private:int* m_ptr;
};A Get(bool flag)
{A a;A b;if (flag){return a;}else{return b;}}int main()
{{A a = Get(false);}return 0;
}

        在上面的代码中,默认构造函数是浅拷贝,a和b会指向同一个指针m_ptr,在析构的时候会导致重复删除该指针,如下所示。

        正确的做法是提供深拷贝的拷贝构造函数,比如下面的代码(关闭返回值优化的情况下):

#include <iostream>
using namespace std;class A
{
public:A():m_ptr(new int(8)){len = 8;cout << "construct m_ptr: " << m_ptr << endl;}///深拷贝A(const A& a) : m_ptr(new int(a.len)){cout << "copy construct m_ptr: " << m_ptr << endl;}~A(){cout << "delete m_ptr: " << m_ptr << endl;delete  m_ptr;}private:int* m_ptr;int len;
};A Get(bool flag)
{A a;A b;if (flag){return a;}else{return b;}}int main()
{A a = Get(false);return 0;
}

  上面的代码将输出:

         这样做就可以保证拷贝构造时的安全性,但又是这种拷贝构造却是不必要的,比如上面的代码中的拷贝构造就是不必要的。上面代码中的Get函数会返回临时变量,然后通过这个临时变量拷贝构造了一个新的对象b,临时变量在拷贝构造完成之后就销毁了,如果堆内存很大,那么,这个拷贝构造的代价会很大,带来了额外的性能损耗。有没有办法避免临时对象的拷贝构造呢?看下面的代码:

#include <iostream>
using namespace std;class A
{
public:A():m_ptr(new int(8)){data = 0;len = 8;cout << "construct m_ptr: " << m_ptr << endl;}///深拷贝A(const A& a) : m_ptr(new int(a.len)){cout << "copy construct m_ptr: " << m_ptr << endl;}A(A&& a):m_ptr(a.m_ptr){a.m_ptr = nullptr;data = 100;cout << "move construct " << endl;}~A(){cout << "delete m_ptr: " << m_ptr << endl;delete  m_ptr;}int data;
private:int* m_ptr;int len;
};A Get(bool flag)
{A a;A b;if (flag){return a;}else{return b;}}int main()
{A a = Get(false);cout << "main func..." << endl;cout << a.data <<endl;return 0;
}

        上面代码将输出:

        上面的代码没有了拷贝构造,取而代之的是移动构造。从移动构造函数的实现中可以看到,它的参数是一个右值引用类型的参数A&&,这里没有深拷贝,只有浅拷贝,这样就避免了对临时对象的深拷贝,提高了性能。这里的A&&用来根据参数是左值还是右值来建立分支,如果是临时值,则会选择移动构造函数。移动构造函数只是将临时对象的资源做了浅拷贝,不需要对其进行深拷贝,从而避免了额外的拷贝,提高了性能。这也就是所谓的移动语义,右值引用的一个重要目的是用来支持移动语义的。

        移动语义可以将资源(堆,系统对象等)通过浅拷贝方式从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建,拷贝以及销毁,可以大幅度提高C++应用程序的性能,消除临时对象的维护(创建和销毁)对性能的影响。

        下面代码实现了拷贝构造函数和拷贝赋值操作符。

#include <iostream>
#include <vector>
#include <string.h>
using namespace std;class MyString
{
public:MyString(){m_data = NULL;m_len = 0;}MyString(const char* p){m_len = strlen(p);copy_data(p);}MyString(const MyString& str){m_len = str.m_len;copy_data(str.m_data);cout << "Copy Construct is called... source: " << str.m_data << endl;}MyString& operator=(const MyString& str){if (this != &str){m_len = str.m_len;copy_data(str.m_data);}cout << "Copy Assignment is called... source: " << str.m_data << endl;return *this;}virtual ~MyString(){if (m_data){delete [] m_data;}}private:void copy_data(const char* s){m_data = new char[m_len + 1];memcpy(m_data, s, m_len);m_data[m_len] = '\0';}private:char*	m_data;size_t	m_len;
};int main()
{MyString a;a = MyString("hello");vector<MyString> v;v.push_back(MyString("world"));return 0;
}

        实现了调用拷贝构造函数的操作和拷贝赋值操作符的操作。MyString("hello")和MyString("world")都是临时对象,也就是右值。虽然它们都是临时的,但程序仍然调用了拷贝构造函数和拷贝赋值函数,造成了没有意义的资源申请和释放操作。如果能够直接使用临时对象已经申请的资源,既能节约资源,又能节省资源申请和释放的时间。这正是定义移动语义的目的。

        用C++11的右值引用来定义这两个函数,代码如下所示。

#include <iostream>
#include <vector>
#include <string.h>
using namespace std;class MyString
{
public:MyString(){m_data = NULL;m_len = 0;}MyString(const char* p){m_len = strlen(p);copy_data(p);}MyString(const MyString& str){m_len = str.m_len;copy_data(str.m_data);cout << "Copy Construct is called... source: " << str.m_data << endl;}MyString& operator=(const MyString& str){if (this != &str){m_len = str.m_len;copy_data(str.m_data);}cout << "Copy Assignment is called... source: " << str.m_data << endl;return *this;}#if 1MyString(MyString&& str){cout << "Move Construct is called... source: " << str.m_data << endl;m_len = str.m_len;m_data = str.m_data;str.m_len = 0;str.m_data = NULL;}MyString& operator==(MyString&& str){cout << "Move Assignmeng is called... source: " << str.m_data <<endl;if (this != &str){m_len = str.m_len;m_data = str.m_data;str.m_len = 0;str.m_data = NULL;}return *this;}
#endifvirtual ~MyString(){if (m_data){delete [] m_data;}}private:void copy_data(const char* s){m_data = new char[m_len + 1];memcpy(m_data, s, m_len);m_data[m_len] = '\0';}private:char*	m_data;size_t	m_len;
};int main()
{MyString a;a = MyString("hello");vector<MyString> v;v.push_back(MyString("world"));return 0;
}

        有了右值引用和移动语义,在设计和实现类时,对于需要动态申请大量资源的类,应该设计右值引用的拷贝构造函数和赋值函数,以提高应用程序的效率。需要注意的是,我们一般在提供右值引用的构造函数时,也会提供常量左值引用的拷贝构造函数,以保证移动不成还可以拷贝构造。

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

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

相关文章

win11系统msvcp120.dll丢失的解决方法,亲测有效的详细方法

在计算机使用过程中&#xff0c;我们常常会遇到一些错误提示&#xff0c;其中之一就是“msvcp120.dll丢失”这个错误通常会导致某些应用程序无法正常运行。为了解决这个问题&#xff0c;我们需要采取一些修复措施。本文将介绍五个修复msvcp120.dll丢失的方法&#xff0c;帮助大…

Pytorch 文本情感分类案例

一共六个脚本,分别是: ①generateDictionary.py用于生成词典 ②datasets.py定义了数据集加载的方法 ③models.py定义了网络模型 ④configs.py配置一些参数 ⑤run_train.py训练模型 ⑥run_test.py测试模型 数据集https://download.csdn.net/download/Victor_Li_/88486959?spm1…

fiddler拦截并浏览器项目的接口请求参数

1、开启拦截模式 2、修改请求 3、放开请求

微服务-Feign

文章目录 Feign介绍Feign的基本使用自定义Feign的配置Feign性能优化Feign最佳实践 Feign介绍 RestTemplate远程调用存在的问题&#xff1a;代码可读性差&#xff0c;java代码中夹杂url&#xff1b;参数复杂很难维护 String url "http://userservice/user/" order.g…

Webpack的入口(entry)和出口(output)

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

markdown增加目录索引,实现点击目录跳转到对应的内容目录标题

文章目录 1. 教程1.1 首先正常编写文章例如如下1.2 原理 2. 示例文件2.1 标题12.1.1 小标题12.1.1.1 小小标题12.1.1.2 小小标题2 2.1.2 小标题2 1. 教程 1.1 首先正常编写文章例如如下 如果使用的是typora则可以直接点击段落-》内容目录&#xff1b;则会自动生成目录 1.2 原理…

chatgpt生成文本的底层工作原理是什么?

文章目录 &#x1f31f; ChatGPT生成文本的底层工作原理&#x1f34a; 一、数据预处理&#x1f34a; 二、模型结构&#x1f34a; 三、模型训练&#x1f34a; 四、文本生成&#x1f34a; 总结 &#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN…

安防监控项目---web网页通过A9控制Zigbee终端节点的风扇

文章目录 前言一、zigbee的CGI接口二、请求线程和硬件控制三、现象展示总结 前言 书接上期&#xff0c;我们可以看一下前面的功能设计的部分&#xff0c;网页端的控制还有一个&#xff0c;那就是通过网页来控制zigbee上的风扇节点&#xff0c;这部分的工作量是相当大的&#x…

如何翻译shader graph到代码并添加额外的效果

使用shader graph翻译到代码可以使用连线工具快速制作效果并转换为代码&#xff0c;如果你对shader的“贴心/详细”报错&#xff0c;以及提示不完善等等问题感到恼火&#xff0c;这个方式或许可以一定程度上缓解以上问题。 本次使用的三个shader graph资源&#xff1a;https:…

美国白宫发布总统令:鼓励AI以安全、可靠的方式发展

美国华盛顿时间10月30日&#xff0c;美国白宫官网发布了&#xff0c;关于发展安全、可靠和值得信赖的AI&#xff08;人工智能&#xff09;的拜登总统行政令。 白宫表示&#xff0c;该行政令为AI安全和保障制定了新标准&#xff0c;保护了用户的数据隐私&#xff0c;促进公平和…

图解Kafka高性能之谜(五)

高性能的多分区、冗余副本集群架构 高性能网络模型NIO 简单架构设计&#xff1a; 详细架构设计&#xff1a; 高性能的磁盘写技术 高性能的消息查找设计 索引文件定位使用跳表的设计 偏移量定位消息时使用稀疏索引&#xff1a; 高响应的磁盘拷贝技术 kafka采用sendFile()的…

Python图像处理【15】基于非锐化掩码锐化图像

基于非锐化掩码锐化图像 0. 前言1. 使用 scikit-image filters 模块执行非锐化掩码2. 使用 PIL ImageFilter 模块执行非锐化掩码3. 使用 SimpleITK 执行拉普拉斯锐化4. 使用 OpenCV 实现非锐化掩码小结系列链接 0. 前言 非锐化滤波器是一个简单的锐化算子&#xff0c;通过从原…