以下内容参考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++右值引用浅析

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

  2. C++ 11 中的右值引用

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

  3. 图说函数模板右值引用参数(T&&)类型推导规则(C++11)

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

  4. c++11的右值引用、移动语义

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

  5. VS2012 error C2664: “std::make_pair”:无法将左值绑定到右值引用

    在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++11 标准新特性: 右值引用与转移语义

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

  8. move语义和右值引用

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

  9. [转载] C++11中的右值引用

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

  10. C++ 11 右值引用

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

随机推荐

  1. centos7.2部署vnc服务记录

    不做过多介绍了,下面直接记录下centos7系统下安装配置vncserver的操作记录 0)更改为启动桌面或命令行模式 获取当前系统启动模式 [root@localhost ~]# systemctl ...

  2. Week 2 代码审查

    我的伙伴是6班的小伙子潘礼鹏,经过几天的相处我觉得真的是说话很有趣的人,性格非常好,我们很划得来. 以下为我对他的代码的审查结果: VS2012与VS2013的兼容性 在这里写一个工具集的问题,不同的 ...

  3. Service Fabric

    Service Fabric 开源 微软的Azure Service Fabric的官方博客在3.24日发布了一篇博客 Service Fabric .NET SDK goes open source ...

  4. Docker attach

    Description Attach local standard input, output, and error streams to a running container Usage dock ...

  5. windows下net命令失败

    D:\apache-tomcat-7.0.57\bin>net start mysql57发生系统错误 5. 拒绝访问. 以管理员身份运行 run as administrator 打开cmd. ...

  6. error loading midas.dll问题

    如果用的delphi在你的单元里用uses midaslib这个东西就可以把midas静态连接到你的程序楼上的也可以 在程序中使用winexec("regsvr32.exe midas.dl ...

  7. charCodeAt与fromCharCode

    charCodeAt() 方法可返回指定位置的字符的 Unicode 编码 这个返回值是 0 - 65535 之间的整数. stringObject.charCodeAt(index) /* a-z  ...

  8. [转帖]中国公有云2018H1市场占有率

    IDC:阿里云中国第一 市场份额为2到9名总和   https://news.cnblogs.com/n/617838/ 1 月 21 日,市场研究机构 IDC 日前公布 2018 年上半年中国公有云 ...

  9. 关于WrapPanel和RadioButton相互配合使用实WrapPanel现动态添加或删除项

    最近在做一个项目的时候,有一个需求就是,通过RadioButton来控制一行内容的显示与不显示,当不显示的时候,下面的项能够占住相应的位置,当增加的时候,又会在原来的位置重新显示,如果使用一般的Gri ...

  10. loadrunner基础学习笔记五-场景

    场景目标:模拟10家旅行社同时登录.搜索航班.购买机票.查看航班路线并退出 负载测试是指在典型工作条件下测试应用程序,例如:多家旅行社同时在同一个机票预订系统中预订机票 controller提供所有用 ...