右值引用&&

时间:2021-12-09 00:34:43

以下内容参考https://blog.csdn.net/china_jeffery/article/details/78520237

右值引用若不作为函数参数使用,基本等于滥用

右值引用 (Rvalue Referene) 是 C++ 新标准 中引入的新特性 , 它实现了移动语义 (Move Sementics) 和完美转发 (Perfect Forwarding)。它的主要目的有两个方面:
  1. 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
  2. 能够更简洁明确地定义泛型函数。

何为右值:

  C++( 包括 C) 中所有的表达式和变量要么是左值,要么是右值。通俗的左值的定义就是非临时对象,
  那些可以在多条语句中使用的对象。 所有的变量都满足这个定义,在多条代码中都可以使用,都是左值。
  右值是指临时的对象,它们只在当前的语句中有效。

int i = ;// 在这条语句中,i 是左值,0 是临时变量,就是右值

在C++11之前,右值是不能引的,如

int &a = ;// error : 非常量的引用必须为左值
const int &a = ;// 我们最多只能用常量引用来绑定一个右值

在C++11中我们可以引用右值,使用&&实现:

int &&a = ;

应用场景:

如下string类,实现了拷贝构造和赋值运算符重载

#include <iostream>
#include <vector>
using namespace std; class MyString
{
public:
MyString()
{
m_data = NULL;
m_len = ;
} MyString(const char* s)
{
m_len = strlen(s);
init_data(s);
cout << "构造函数" << s << endl;
} MyString(const MyString& str)
{
m_len = str.m_len;
init_data(str.m_data);
cout << "拷贝构造函数" << str.m_data << endl;
} MyString& operator=(const MyString& str)
{
if ( this != &str ){
this->m_len = str.m_len;
init_data(str.m_data);
}
cout << "等号操作符重载" << str.m_data << endl;
return *this;
} ~MyString()
{
if ( m_data != NULL ){
cout << "析构函数" << endl;
free(m_data);
}
} private:
void init_data(const char* s)
{
m_data = new char[m_len + ];
memcpy(m_data, s, m_len);
m_data[m_len] = '\0';
}
char* m_data;
size_t m_len;
}; void test()
{
vector<MyString> vec;
MyString a;// 没有输出
a = MyString("hello");
vec.push_back(MyString("world"));
} int main()
{
test(); system("pause");
return ;
}

输出:
构造函数hello
等号操作符重载hello
析构函数
构造函数world
拷贝构造函数world
析构函数
析构函数
析构函数

总共执行了2次拷贝,MyString("Hello")和MyString("World")都是临时对象,临时对象被使用完之后会被立即析构,在析构函数中free掉申请的内存资源。
如果能够直接使用临时对象已经申请的资源,并在其析构函数中取消对资源的释放,这样既能节省资源,有能节省资源申请和释放的时间。 这正是定义移动语义的目的。

通过加入定义移动构造函数和转移赋值操作符重载来实现右值引用(即复用临时对象):

#include <iostream>
#include <vector>
using namespace std; class MyString
{
public:
MyString()
{
m_data = NULL;
m_len = ;
} MyString(const char* s)
{
m_len = strlen(s);
init_data(s);
cout << "构造函数" << s << endl;
} MyString(MyString&& str)
{
cout << "移动构造函数" << str.m_data << endl;
m_len = str.m_len;
m_data = str.m_data;
str.m_len = ;
str.m_data = NULL;// 防止在析构函数中将内存释放掉
} MyString& operator=(MyString&& str)
{
cout << "移动等号操作符重载" << str.m_data << endl;
if ( this != &str ){
this->m_len = str.m_len;
this->m_data = str.m_data;
str.m_len = ;
str.m_data = NULL;// 防止在析构函数中将内存释放掉
}
return *this;
} ~MyString()
{
if ( m_data != NULL ){
cout << "析构函数" << endl;
free(m_data);
}
} private:
void init_data(const char* s)
{
m_data = new char[m_len + ];
memcpy(m_data, s, m_len);
m_data[m_len] = '\0';
}
char* m_data;
size_t m_len;
}; void test()
{
vector<MyString> vec;
MyString a;// 没有输出
a = MyString("hello");
vec.push_back(MyString("world"));
} int main()
{
test(); system("pause");
return ;
}

输出:
构造函数hello
移动等号操作符重载hello
构造函数world
移动拷贝构造函数world
析构函数
析构函数

需要注意的是:右值引用并不能阻止编译器在临时对象使用完之后将其释放掉的事实,
所以移动构造函数和移动赋值操作符重载函数 中都将_data赋值为了NULL,而且析构函数中保证了_data != NULL才会释放。

标准库函数std::move

  既然编译器只对右值引用才能调用移动构造函数和移动赋值函数,又因为所有命名对象都只能是左值引用。
  在这样的条件了,如果已知一个命名对象不再被使用而想对它调用转移构造函数和转移赋值函数
  也就是把一个左值引用当做右值引用来使用,怎么做呢?
  标准库提供了函数 std::move,这个函数以非常简单的方式将左值引用转换为右值引用。

#include <iostream>
using namespace std; void ProcessValue(int& i)
{
cout << "LValue processed:" << i << endl;
} void ProcessValue(int&& i)
{
cout << "RValue processed:" << i << endl;
} int main()
{
int a = ;
ProcessValue(a);
ProcessValue(move(a)); system("pause");
return ;
}

输出:

LValue processed:1

RValue processed:1

std::move在提高swap函数性能上有非常大的帮助,一般来说,swap函数的通用定义如下:

template <class T>
void swap(T& a, T& b)
{
T tmp(a);// copy a to tmp
a = b;// copy b to a
b = tmp;//copy tmp to b
}

结合std::move 和 右值引用,可以避免不必要的拷贝。swap的定义变为:

#include <iostream>
#include <vector>
using namespace std; class MyString
{
public:
MyString()
{
m_data = NULL;
m_len = ;
} MyString(const char* s)
{
m_len = strlen(s);
init_data(s);
cout << "构造函数" << s << endl;
} MyString(MyString&& str)
{
cout << "移动构造函数" << str.m_data << endl;
m_len = str.m_len;
m_data = str.m_data;
str.m_len = ;
str.m_data = NULL;// 防止在析构函数中将内存释放掉
} MyString& operator=(MyString&& str)
{
cout << "移动等号操作符重载" << str.m_data << endl;
if ( this != &str ){
this->m_len = str.m_len;
this->m_data = str.m_data;
str.m_len = ;
str.m_data = NULL;// 防止在析构函数中将内存释放掉
}
return *this;
} ~MyString()
{
if ( m_data != NULL ){
cout << "析构函数" << endl;
free(m_data);
}
} private:
void init_data(const char* s)
{
m_data = new char[m_len + ];
memcpy(m_data, s, m_len);
m_data[m_len] = '\0';
}
char* m_data;
size_t m_len;
}; namespace MyT{
template <class T>
void swap(T& a, T& b)
{
T tmp(std::move(a));// move a to tmp
a = std::move(b);// move b to a
b = std::move(tmp);//move tmp to b
}
} void test()
{
MyString a("hello");
MyString b("world");
MyT::swap<MyString>(a, b);
} int main()
{
test(); system("pause");
return ;
}

精确传递(perfect Forwarding)

  精确传递就是在参数传递过程中,所有这些属性和参数值都不能改变。在泛型函数中,这样的需求非常普遍

  forward_value函数只有一个参数val,定义如下:

template <typename T>
void forward_value(const T& val) {
process_value(val);
} template <typename T>
void forward_value(T& val) {
process_value(val);
}

函数 forward_value 为每一个参数必须重载两种类型,T& 和 const T&,否则,下面四种不同类型参数的调用中就不能同时满足:

int a = ;
const int &b = ;
forward_value(a); // int&
forward_value(b); // const int&
forward_value(); // int&

对于一个参数就要重载两次,也就是函数重载的次数和参数的个数是一个正比的关系。这个函数的定义次数对于程序员来说,是非常低效的。我们看看右值引用如何帮助我们解决这个问题:

template <typename T>
void forward_value(T&& val) {
process_value(val);
}

只需要定义一次,接受一个右值引用的参数,就能够将所有的参数类型原封不动的传递给目标函数。

右值引用&&的更多相关文章

  1. C&plus;&plus;右值引用浅析

    一直想试着把自己理解和学习到的右值引用相关的技术细节整理并分享出来,希望能够对感兴趣的朋友提供帮助. 右值引用是C++11标准中新增的一个特性.右值引用允许程序员可以忽略逻辑上不需要的拷贝:而且还可以 ...

  2. C&plus;&plus; 11 中的右值引用

    C++ 11 中的右值引用 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream>    #include &l ...

  3. 图说函数模板右值引用参数&lpar;T&amp&semi;&amp&semi;&rpar;类型推导规则(C&plus;&plus;11)

    见下图: 规律总结: 只要我们传递一个基本类型是A④的左值,那么,传递后,T的类型就是A&,形参在函数体中的类型就是A&. 只要我们传递一个基本类型是A的右值,那么,传递后,T的类型就 ...

  4. c&plus;&plus;11的右值引用、移动语义

    对于c++11来说移动语义是一个重要的概念,一直以来我对这个概念都似懂非懂.最近翻翻资料感觉突然开窍,因此记下.其实搞懂之后就会发现这个概念很简单,并无什么高深的地方. 先说说右值引用.右值一般指的是 ...

  5. VS2012 error C2664&colon; &OpenCurlyDoubleQuote;std&colon;&colon;make&lowbar;pair”&colon;无法将左值绑定到右值引用

    在vs2012(c++)make_pair()改动: C++: template <class T1, class T2> pair<V1, V2> make_pair(T1& ...

  6. 右值引用、move与move constructor

    http://blog.chinaunix.net/uid-20726254-id-3486721.htm 这个绝对是新增的top特性,篇幅非常多.看着就有点费劲,总结更费劲. 原来的标准当中,参数与 ...

  7. 【转】C&plus;&plus;11 标准新特性&colon; 右值引用与转移语义

    VS2013出来了,对于C++来说,最大的改变莫过于对于C++11新特性的支持,在网上搜了一下C++11的介绍,发现这篇文章非常不错,分享给大家同时自己作为存档. 原文地址:http://www.ib ...

  8. move语义和右值引用

    C++11支持move语义,用以避免非必要拷贝和临时对象. 具体内容见收藏中的“C++右值引用” .

  9. &lbrack;转载&rsqb; C&plus;&plus;11中的右值引用

    C++11中的右值引用 May 18, 2015 移动构造函数 C++98中的左值和右值 C++11右值引用和移动语义 强制移动语义std::move() 右值引用和右值的关系 完美转发 引用折叠推导 ...

  10. C&plus;&plus; 11 右值引用

    C++11中引入的一个非常重要的概念就是右值引用.理解右值引用是学习“移动语义”(move semantics)的基础.而要理解右值引用,就必须先区分左值与右值. 注意:左值右值翻译可能有些问题 *L ...

随机推荐

  1. 数据终端设备与无线通信模块之间串行通信链路复用协议(TS27&period;010)在嵌入式系统上的开发【转】

    转自:http://blog.csdn.net/hellolwl/article/details/6164449 目录(?)[-] 协议介绍 模块协议介绍 1            命令包格式 2   ...

  2. C&num; 自定义FileUpload控件

    摘要:ASP.NET自带的FileUpload控件会随着浏览器的不同,显示的样式也会发生改变,很不美观,为了提高用户体验度,所以我们会去自定义FileUpload控件 实现思路:用两个Button和T ...

  3. Scrum团队成立

    1.团队名称:瘦子不吃肥肉 目标:学习更多,了解更多! 口号:加油 团队照: 2.角色分配: 产品负责人:卓嘉炜        http://www.cnblogs.com/luoliuxi/ Scr ...

  4. UVa 10601 &lpar;Polya计数 等价类计数&rpar; Cubes

    用6种颜色去染正方体的12条棱,但是每种颜色都都限制了使用次数. 要确定正方体的每一条棱,可以先选择6个面之一作为顶面,然后剩下的四个面选一个作为前面,共有24种. 所以正方体的置换群共有24个置换. ...

  5. 【转】VI&sol;VIM常用命令

    原文网址:http://www.blogjava.net/woxingwosu/archive/2007/09/06/125193.html Vi是“Visual interface”的简称,它在Li ...

  6. Js点餐加减数量

    <button class="add-on" onclick="chgNum(1,'del')" ><i class="icon-m ...

  7. PHP开发调试环境配置

    ——基于wamp和Eclipse for PHP Developers 引言 为了搭建PHP开发调试环境,我曾经在网上查阅了无数的资料,但没有一种真正能够行的通的.因为PHP开发环境需要很多种软件相互 ...

  8. &lpar;转&rpar;thymeleaf中的判断总结

    判断String字符串,添加引号 th:class="${flag=='forum.html'}?'active'" 判断boolean类型,注意不能当成字符串处理,不能添加引号 ...

  9. h5 的localStorage和sessionStorage存到缓存里面的值是string类型

    localStorage永久存在,不手动清除永远存在:sessionStorage 一次会话的浏览器关闭就自动清除 h5 的localStorage和sessionStorage 存到缓存里面的值都是 ...

  10. nodejs--路径问题

    在读写模块中,需要引入读写文件,此时需要注意路径问题.Node.js中为我们提供了两个参数:__dirname和__filename. __dirname:全局变量,存储的是文件所在的文件目录 __f ...