(原创)C++11改进我们的程序之右值引用
本次主要讲c++11中的右值引用,后面还会讲到右值引用如何结合std::move优化我们的程序。
c++11增加了一个新的类型,称作右值引用(R-value reference),标记为T &&,说到右值引用类型之前先要了解什么是左值和右值。
左值具名,对应指定内存域,可访问;右值不具名,不对应内存域,不可访问。临时对像是右值。左值可处于等号左边,右值只能放在等号右边。区分表达式的左右值属性有一个简便方法:若可对表达式用 & 符取址,则为左值,否则为右值。
1.简单的赋值语句
如:int i = 0;
在这条语句中,i 是左值,0 是临时值,就是右值。在下面的代码中,i 可以被引用,0 就不可以了。立即数都是右值。
2.右值也可以出现在赋值表达式的左边,但是不能作为赋值的对象,因为右值只在当前语句有效,赋值没有意义。
如:((i>0) ? i : j) = 1;
在这个例子中,0 作为右值出现在了”=”的左边。但是赋值对象是 i 或者 j,都是左值。
在 C++11 之前,右值是不能被引用的,最大限度就是用常量引用绑定一个右值,如 :
const int &a = 1;
在这种情况下,右值不能被修改的。但是实际上右值是可以被修改的,既然右值可以被修改,那么就可以实现右值引用。右值引用能够方便地解决实际工程中的问题。
int && a = 1; //&&为右值引用
&&的特性
实际上T&&并不是一定表示右值引用,它的引用类型是未定的,即可能是左值有可能是右值。看看这个例子:
template<typename T>
void f(T&& param); f(); //10是右值
int x = ;
f(x); //x是左值
从这个例子可以看出,param有时是左值引用,有时是右值引用,它在上面的例子中&&实际上是一个未定的引用类型。这个未定的引用类型被scott meyers称为universal references(可以认为它是种通用的引用类型),它必须被初始化,它是左值应用还是右值引用取决于它的初始化,如果&&被一个左值初始化的话,它就是一个左值引用;如果它被一个右值初始化的话,它就是一个右值引用。
&&为universal references时的唯一条件是有类型推断发生。
template<typename T>
void f(T&& param); //这里T的类型需要推导,所以&&是一个universal references template<typename T>
class Test {
...
Test(Test&& rhs); // 已经定义了一个特定的类型, 没有类型推断
... // && 是一个右值引用
}; void f(Test&& param); // 已经定义了一个确定的类型, 没有类型推断,&& 是一个右值引用
再看一个复杂一点的例子
template<typename T>
void f(std::vector<T>&& param);
这里既有推断类型T又有确定类型vector,那么这个param到底是什么类型呢?
它是右值引用类型,因为在调用这个函数之前,这个vector<T>中的推断类型已经确定了,所以到调用f时没有类型推断了。
再看看这个例子:
template<typename T>
void f(const T&& param);
这个param是universal references吗?错,它是右值引用类型,也许会迷糊,T不是推断类型吗,怎么会是右值引用类型。其实还有一条规则:universal references仅仅在T&&下发生,任何一点附加条件都会使之失效,而变成一个右值引用。
引用折叠(Reference collapsing)规则:
- 所有的右值引用叠加到右值引用上变成一个右值引用
- 所有的其它引用类型叠加都变成一个左值引用
- 左值或者右值是独立于它的类型的,也就是说一个右值引用类型的左值是合法的。
int&& var1 = x; // var1 is of type int&& (no use of auto here)
auto&& var2 = var1; // var2 is of type int& ,var2的类型是universal references(有类型推导)
var1的类型是一个左值类型,但var1本身是一个左值;
var1是一个左值,根据引用折叠规则,var2是一个int&
int w1, w2;
auto&& v1 = w1;
decltype(w1)&& v2 = w2;
v1是一个universal reference,它被一个左值初始化,所以它最终一个左值;
v2是一个右值引用类型,但它被一个左值初始化,一个左值初始化一个右值引用类型是不合法的,所以会编译报错。但是如果我希望把一个左值赋给一个右值引用类型该怎么做呢
,用std::move,decltype(w1)&& v2 = std::move(w2); std::move可以将一个左值转换成右值,关于std::move将在下一篇博文中介绍。
&&的总结:
- 左值和右值是独立于它们的类型的,一个左值的类型有可能是右值引用类型。
- T&&是一个未定的引用类型,它可能是左值引用也可能是右值引用类型,取决于初始化的值类型。
- &&成为未定的引用类型的唯一条件是:T&&且发生类型推断。
- 所有的右值引用叠加到右值引用上变成一个右值引用,其它引用折叠都为左值引用。
如果想更详细了解&&,可以参考scott-meyers这个文章:http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
右值引用优化性能,避免深拷贝
右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。消除了临时对象的维护 ( 创建和销毁 ) 对性能的影响。
以一个简单的 string 类为示例,实现拷贝构造函数和拷贝赋值操作符。
class MyString {
private:
char* m_data;
size_t m_len;
void copy_data(const char *s) {
m_data = new char[m_len+];
memcpy(_data, s, m_len);
m_data[_len] = '\0';
}
public:
MyString() {
m_data = NULL;
m_len = ;
} 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);
std::cout << "Copy Constructor is called! source: " << str.m_data << std::endl;
} MyString& operator=(const MyString& str) {
if (this != &str) {
m_len = str.m_len;
copy_data(str._data);
}
std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl;
return *this;
} virtual ~MyString() {
if (m_data) free(m_data);
}
};
void test() {
MyString a;
a = MyString("Hello");
std::vector<MyString> vec;
vec.push_back(MyString("World"));
}
实现了调用拷贝构造函数的操作和拷贝赋值操作符的操作。MyString(“Hello”) 和 MyString(“World”) 都是临时对象,也就是右值。虽然它们是临时的,但程序仍然调用了拷贝构造和拷贝赋值,造成了没有意义的资源申请和释放的操作。如果能够直接使用临时对象已经申请的资源,既能节省资源,有能节省资源申请和释放的时间。这正是定义转移语义的目的。
用c++11的右值引用来定义这两个函数
MyString(MyString&& str) {
std::cout << "Move Constructor is called! source: " << str._data << std::endl;
_len = str._len;
_data = str._data; //避免了不必要的拷贝
str._len = ;
str._data = NULL;
}
MyString& operator=(MyString&& str) {
std::cout << "Move Assignment is called! source: " << str._data << std::endl;
if (this != &str) {
_len = str._len;
_data = str._data; //避免了不必要的拷贝
str._len = ;
str._data = NULL;
}
return *this;
}
有了右值引用和转移语义,我们在设计和实现类时,对于需要动态申请大量资源的类,应该设计右值引用的拷贝构造函数和赋值函数,以提高应用程序的效率。
c++11 boost技术交流群:296561497,欢迎大家来交流技术。
(原创)C++11改进我们的程序之右值引用的更多相关文章
- 对C++11中的`移动语义`与`右值引用`的介绍与讨论
本文主要介绍了C++11中的移动语义与右值引用, 并且对其中的一些坑做了深入的讨论. 在正式介绍这部分内容之前, 我们先介绍一下rule of three/five原则, 与copy-and-swap ...
- C++11新特性(1) 右值引用
在C++中,左值(lvalue)是能够获取其地址的一个量.因为常常出如今赋值语句的左边.因此称之为左值.比如一个有名称的变量. 比如: int a=10; //a就是一个左值. 传统的C++引用,都是 ...
- (原创)c++11改进我们的程序之垃圾回收
c#和java中有自动垃圾回收机制,.net运行时和java虚拟机可以管理分配的堆内存,在对象失去引用时自动回收,因此在c#和jva中, 内存管理不是大问题.c++语言没有垃圾回收机制,必须自己去释放 ...
- c++11——右值引用
1. 左值和右值 左值是表达式结束之后仍然存在的持久化对象,而右值是指表达式结束时就不再存在的临时对象. c++11中,右值分为两种类型:将亡值(xvalue, expiring value) ...
- c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用
为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #includ ...
- C++11标准之右值引用(rvalue reference)
1.右值引用引入的背景 临时对象的产生和拷贝所带来的效率折损,一直是C++所为人诟病的问题.但是C++标准允许编译器对于临时对象的产生具有完全的自由度,从而发展出了Copy Elision.RVO(包 ...
- [c++11]右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- [转][c++11]我理解的右值引用、移动语义和完美转发
c++中引入了右值引用和移动语义,可以避免无谓的复制,提高程序性能.有点难理解,于是花时间整理一下自己的理解. 左值.右值 C++中所有的值都必然属于左值.右值二者之一.左值是指表达式结束后依然存在的 ...
- (原创)C++11改进我们的程序之简化我们的程序(八)
本次要讲的是如何通过泛型函数来简化我们的程序. 泛型函数除了之前介绍的一些优点外还有两个重要的优点 1.消除重复逻辑,提高程序的内聚性和健壮性 泛型函数在某种程度上用来弥补泛型类型的不足.通过泛型类型 ...
随机推荐
- Spring Cloud开发实践 - 04 - Docker部署
Docker的安装和命令可以参考 https://www.cnblogs.com/milton/p/9866963.html . 资源规划 这一步要区分传统资源和Docker资源, 为后面的细节定好基 ...
- ios中第三方库归结
1:uiscrollview 折叠 展开中不包含tablecell. 展开列表效果 Collapse Click () https://github.com/bennyguitar/Collapse ...
- Intel Galileo驱动单总线设备(DHT11\DHT22)(转)
Intel Galileo一代的IO翻转速度不够,无法直接驱动单总线设备,二代听说改进了,但没有库,于是国外开发者想出了另一种法子,转过来给大家学习下.如果后面有时间,再来翻译.原文地址:http:/ ...
- 【java】深入了解JAVA可变长度的参数
到J2SE 1.4为止,一直无法在Java程序里定义实参个数可变的方法——因为Java要求实参(Arguments)和形参(Parameters)的数量和类型都必须逐一匹配,而形参的数目是在定义方法时 ...
- sublime Text 些许使用配置
在安装numpy等库函数时,通过“命令提示符”操作显示库函数已经安装完毕,在pycharm中可是依然显示引用失败,尝试使用sublime,显示可用,遂好好使用sublime,现配置成想用的模式. 1 ...
- U811.1接口EAI系列之一--通用把XML传送给EAI处理方法--PowerBuilder语言
1.前面配置参考:http://www.cnblogs.com/spring_wang/p/3393147.html 2.pb通用调EAI方法代码如下: //===================== ...
- 如何获取 docker 容器(container)的 ip 地址(转)
1. 进入容器内部后 cat /etc/hosts 会显示自己以及(– link)软连接的容器IP 2.使用命令 docker inspect --format '{{ .NetworkSetting ...
- mapreduce 只使用Mapper往多个hbase表中写数据
只使用Mapper不使用reduce会大大减少mapreduce程序的运行时间. 有时候程序会往多张hbase表写数据. 所以有如题的需求. 下面给出的代码,不是可以运行的代码,只是展示driver中 ...
- 收集一些有意思的ASCII程序注释(持续收集中,希望大家踊跃贡献)
/** * * created by Mr.Simple, Aug 21, 20141:51:40 PM. * Copyright (c) 2014, hehonghui@umeng.com Al ...
- 安装CDH5时出错 5.68.168.192.in-addr.arpa domain name pointer bogon.
这个是因为使用的DNS是联通的server,联通会将所有保留的网络地址RFC1918( http://tools.ietf.org/html/rfc1918)都指向"bogon" ...