以下内容参考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. 基于Nginx+Keepalived的LB服务监控(邮件报警)

    IDC两台机器上部署了Nginx+Keepalived主从模式的LB代理负载层,现在需要对LB进行每日巡检和服务监控,利用SendEmail邮件监控. 0)SendEmail部署 参考:http:// ...

  2. Python_闭包_27

    #闭包:嵌套函数,内部函数 并且必须调用外部函数的变量 def outer(): a = 1 def inner(): print(a) inner() print(inner.__closure__ ...

  3. 个人博客作业Week3--必应词典案例分析

    第一部分  调研,评测 (软件的bug,功能评测,黑箱测试,第8章 用户调研,12 章软件的用户体验) 下载并使用,按照描述的bug定义,找出几个功能性的比较严重的bug.至少两个.用专业的语言描述( ...

  4. 《linux内核设计与实现》第三章

    1.进程 进程就是正在执行的程序代码的实时结果,不仅包含可执行代码,还包括其他资源,比如:打开的文件,挂起的信号,内核内部数据结构,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程 ...

  5. springboot整合druid连接池、mybatis实现多数据源动态切换

    demo环境: JDK 1.8 ,Spring boot 1.5.14 一 整合durid 1.添加druid连接池maven依赖 <dependency> <groupId> ...

  6. Spring MVC静态资源处理(转)

    原文地址: http://www.cnblogs.com/fangqi/archive/2012/10/28/2743108.html 优雅REST风格的资源URL不希望带 .html 或 .do 等 ...

  7. 实战框架ABP

    abp及实战框架概述 接触abp也快一年了,有过大半年的abp项目开发经验,目前项目中所用的abp框架版本为0.10.3,最新的abp框架已经到了1.4,并且支持了asp.net core.关于abp ...

  8. 面象对象设计原则之一:单一职责原则(Single Responsibility Principle, SRP)

    单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小.单一职责原则定义如下:单一职责原则(Single Responsibility Principle, SRP):一个类只负责一个功能领域 ...

  9. 用Axios Element 实现全局的请求 loading

        Kapture 2018-06-07 at 14.57.40.gif demo in github 背景 业务需求是这样子的,每当发请求到后端时就触发一个全屏的 loading,多个请求合并为 ...

  10. NF5280M4 安装 Win2016 的方法

    1. 前提条件, 硬盘大于2T, 2. 必须使用最新版本的 Win2016 首先 win2016的可用序列号 • Windows Server 数据中心 CB7KF-BWN84-R7R2Y-793K2 ...