C++11中右值引用和移动语义
目录
- 左值、右值、左值引用、右值引用
- 右值引用和统一引用
- 使用右值引用,避免深拷贝,优化程序性能
- std::move()移动语义
- std::forward()完美转发
- 容器中的emplace_back()
C++11增加了一个新的类型,称作右值引用(R-value reference),标记为T&&,右值引用结合std::move可以很好的优化程序的效率。
1.左值、右值、左值引用、右值引用
左值是有名字的,对应了一定的内存区域,可访问;右值不具名,不对应内存域,不可访问,临时对像是右值。区分表达式的左右值属性有一个简便方法:若可对表达式用 & 符取址,则为左值,否则为右值。左值引用是对左值的引用,右值引用是对右值的引用。在C++11之前只存在左值引用,常量的引用是不合法的,在C++11中引入了右值引用。
const int& cra = ;
int& ra = ;//error
int && a = ; //&&为右值引用
2.右值引用和统一引用
“T&&”有两个不同的含义。一个当然是右值引用,这个引用表现出你所期望的:它们仅仅绑定到右值,它们的主要差事就是识别出那些可以被移动的对象。“T&&”另一个含义是既是右值引用,又是左值引用,称为统一引用。这样的引用在代码中看上去像右值引用(也就是T&&),但它们可以表现的像是左值引用。它们的双重特性使之可以既绑定到右值(像右值引用一样),也可以绑定到左值(像左值引用)。统一引用出现在两种场景下,最常见的是函数模板的参数,第二个场景是auto声名,两个场景共同之处是有类型推导的出现。在模板f中,param的类型是推导的,对var2的声明中,var2的类型也是推导的,如果想更详细了解&&,可以参考scott-meyers这个文章:http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
void f(Widget&& param); // 右值引用
Widget&& var1 = Widget(); // 右值引用
template<typename T>
void f(std::vector<T>&& param); // 右值引用
template<typename T>
void f(T&& param); // 统一引用
auto&& var2 = var1; // 统一引用
3.右值引用,避免深拷贝
右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。消除了临时对象的维护 ( 创建和销毁 ) 对性能的影响。以一个简单的 string 类为示例,实现拷贝构造函数和拷贝赋值操作符。实现了调用拷贝构造函数的操作和拷贝赋值操作符的操作。MyString(“Hello”) 和 MyString(“World”) 都是临时对象,也就是右值。虽然它们是临时的,但程序仍然调用了拷贝构造和拷贝赋值,造成了没有意义的资源申请和释放的操作。如果能够直接使用临时对象已经申请的资源,既能节省资源,有能节省资源申请和释放的时间。这正是定义转移语义的目的。
#include <iostream>
#include <cstring>
#include <vector>
using namespace std; class MyString {
private:
char* _data;
public:
//default constructor
MyString() :
_data(new char[]) {
*_data = '\0';
std::cout << "default constructor" << std::endl;
}
MyString(const char* str) :
_data(new char[strlen(str) + ]) {
std::cout << "constructor" << std::endl;
strcpy(_data, str);
} //copy constructor
MyString(const MyString& rhs) :
_data(new char[rhs.size() + ]) {
std::cout << "copy constructor" << endl;
strcpy(this->_data, rhs.c_str());
} MyString(MyString&& rhs){
std::cout << "move copy constructor" << std::endl;
this->_data = rhs._data;
rhs._data = nullptr;
} MyString& operator=(MyString&& rhs){
std::cout << "move assign " << std::endl;
if(this->_data != rhs._data){
this->_data = rhs._data;
rhs._data = nullptr;
}
return *this;
}
~MyString() {
delete[] _data;
} //assign
MyString& operator=(const MyString& rhs) {
std::cout << "assign" << std::endl;
if (this->_data == rhs._data) {
return *this;
}
delete[] this->_data;
this->_data = new char[rhs.size() + ];
strcpy(this->_data, rhs._data);
return *this;
} bool operator==(const MyString& rhs) {
if (strcmp(this->_data, rhs._data) == ) {
return true;
} else {
return false;
}
} char operator[](size_t index) {
if (index >= strlen(_data)) {
return '';
} else {
return _data[index];
}
} friend MyString operator+(const MyString& lhs, const MyString& rhs) {
MyString str;
str._data = new char[lhs.size() + rhs.size() + ];
strcpy(str._data, lhs._data);
strcat(str._data, rhs._data);
return str;
} friend ostream& operator<<(ostream &out, const MyString& str) {
out << str._data;
return out;
} size_t size() const {
return strlen(_data);
} const char* c_str() const {
return _data;
}
};
int main() { MyString str1("hello"); //constructor
MyString str2(str1); //copy constructor MyString str3 = MyString("abc"); //constructor
MyString str4 = std::move(MyString("aba")); //constructor,move copy constructor; str2 = std::move(str3); //move assign
str2 = MyString("abc"); //constructor,move assign vector<MyString> v;
v.push_back(MyString("world")); //constructor,move copy constructor; return ;
}
4.std::move()移动语义
std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝。这种移动语义是很有用的,比如我们一个对象中有一些指针资源或者动态数组,在对象的赋值或者拷贝时就不需要拷贝这些资源了。在c++11之前我们的拷贝构造函数和赋值函数可能要这样定义:假设一个A对象内部有一个资源m_ptr;
A& A::operator=(const A& rhs)
{
// 销毁m_ptr指向的资源
// 复制rhs.m_ptr所指的资源,并使m_ptr指向它
}
上面的过程是可行的,但是更有效率的办法是直接交换a和临时对象中的资源指针,然后让临时对象的析构函数去销毁a原来拥有的资源。换句话说,当赋值操作符的右边是右值的时候,我们希望赋值操作符被定义成下面这样:
A& A::operator=(const A&& rhs)
{
// 仅仅转移资源的所有者,将资源的拥有者改为被赋值者 }
这就是所谓的move语义。再看一个例子,假设一个临时容器很大,赋值给另一个容器。
{
std::list< std::string > tokens;//省略初始化...
std::list< std::string > t = tokens;
}
std::list< std::string > tokens;
std::list< std::string > t = std::move(tokens);
5.std::forword()完美转发
右值引用类型是独立于值的,一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值了,并不是它原来的类型了。因此,我们需要一种方法能按照参数原来的类型转发到另一个函数,这种转发被称为完美转发。所谓完美转发(perfect forwarding),是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数。C++11中提供了这样的一个函数std::forward,它是为转发而生的,它会按照参数本来的类型来转发出去,不管参数类型是T&&这种未定的引用类型还是明确的左值引用或者右值引用。
template<typename T>
void print(T& t) {
std::cout << "lvalue" << std::endl;
} template<typename T>
void print(T&& t) {
std::cout << "rvalue" << std::endl;
} template<typename T>
void testForward(T && t) {
print(t);
print(std::forward<T>(t));
print(std::move(t));
}
6. 成员的emplace_back
c++11中大部分容器都加了一个emplace_back成员函数,vector中它的定义是这样的:
template< class... Args >
void emplace_back( Args&&... args );
这里的Args&&是一个未定的引用类型,因此它可以接收左值引用和右值引用,它的内部也是调用了std::forward实现完美转发的。因此如果我们需要往容器中添加右值、临时变量时,用emplace_back可以提高性能。
参考:
C++11中右值引用和移动语义的更多相关文章
- 关于C++11右值引用和移动语义的探究
关于C++11右值引用和移动语义的探究
- [c++11]右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- C++11 右值引用和转移语义
新特性的目的 右值引用 (Rvalue Referene) 是 C++ 新标准 (C++11, 11 代表 2011 年 ) 中引入的新特性 , 它实现了转移语义 (Move Sementics) 和 ...
- C++11 右值引用 与 转移语义
新特性的目的 右值引用(R-value Reference)是C++新标准(C++11, 11代表2011年)中引入的新特性,它实现了转移语义(Move Semantics)和精确传递(Perfect ...
- c++11 右值引用和移动语义
什么是左值.右值 最常见的误解: 等号左边的就是左值,等号右边的就是右值 左值和右值都是针对表达式而言的, 左值是指表达式结束后依然存在的持久对象 右值是指表达式结束时就不再存在的临时对象区分: 能对 ...
- [转][c++11]我理解的右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- 【转】C++11 标准新特性: 右值引用与转移语义
VS2013出来了,对于C++来说,最大的改变莫过于对于C++11新特性的支持,在网上搜了一下C++11的介绍,发现这篇文章非常不错,分享给大家同时自己作为存档. 原文地址:http://www.ib ...
- C++11 标准新特性: 右值引用与转移语义
文章出处:https://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/ 新特性的目的 右值引用 (Rvalue Referene) ...
- C++11新特性之右值引用(&&)、移动语义(move)、完美转换(forward)
1. 右值引用 个人认为右值引用的目的主要是为了是减少内存拷贝,优化性能. 比如下面的代码: String Fun() { String str = "hello world"; ...
随机推荐
- hdu 5001 从任意点出发任意走d步不经过某点概率
http://acm.hdu.edu.cn/showproblem.php?pid=5001 给定n个点m条边的无向图问从任意点出发任意走d步,从不经过某个点的概率 本想先算路过每个点的概率然后用1减 ...
- bootstrap modal
模态框提供了两个可选尺寸,通过为 .modal-dialog 增加一个样式调整类实现.加modal-lg,加modal-sm,不加也可以,共有三种尺寸. 触发方式,data-target, 感觉比js ...
- Android 体系架构
什么是Android? 答:Android就是移动设备的软件栈,包括(一个完整的操作系统,中间件,关键应用程序), 底层是Linux内核,包括(安全管理, 内存管理,进程管理 ,电源管理,硬件驱动-) ...
- [ACM_模拟] UVA 12504 Updating a Dictionary [字符串处理 字典增加、减少、改变问题]
Updating a Dictionary In this problem, a dictionary is collection of key-value pairs, where keys ...
- 创建TFS备份计划失败,错误提示:TF400997
问题描述 在一个TFS 2018 + SQL Server 2017的环境中,从TFS控制台中配置备份计划时,系统提示错误TF400997,需要授予数据库服务账户sqlservice@domain.c ...
- Java的动态编译、动态加载、字节码操作
想起来之前做的一个项目:那时候是把需要的源代码通过文件流输出到一个.java文件里,然后调用sun的Comipler接口动态编译成.class文件,然后再用专门写的一个class loader加载这个 ...
- Nigix配置
- HDU 6198(2017 ACM/ICPC Asia Regional Shenyang Online)
思路:找规律发现这个数是斐波那契第2*k+3项-1,数据较大矩阵快速幂搞定. 快速幂入门第一题QAQ #include <stdio.h> #include <stdlib.h& ...
- libevent源码剖析
libevent是一个使用C语言编写的,轻量级的开源高性能网络库,使用者很多,研究者也很多.由于代码简洁,设计思想简明巧妙,因此很适合用来学习,提升自己C语言的能力. libevent有这样显著地几个 ...
- Android多媒体整体架构图
Android多媒体整体架构图 MediaPlayer框架图 Camera框架图 SoundRecorder框架图 VideoCamera框架图 OpenCore与Skia ALSA Audio框架图 ...