C++11除了带来了右值引用以外,还引入了一种称为“万能引用”的语法;通过“万能引用”,对某型别的引用T&&,既可以表达右值引用,也可以表达左值引用。

定义

该语法有两种使用场景,最常见的一种是作为函数模板的形参:

template<typename T>
void f(T&& param);

其中param就是一个万能引用。

第二个场景则是auto声明:

auto&& var2 = var1;

这两种情况都涉及到了型别的推导,也就是说,如果你虽然遇到了T&&的形式,但是不涉及型别推导,那么它只是一个右值引用:

void f(Widget&& param);

同时,想要成为万能引用,变量声明的形式也必须正确无误,必须正好是形如“T&&”才行。比如下面这些情况就不是万能引用:

template<typename T>
void f1(std::vector<T>&& param); // param是一个右值引用 template<typename T>
void f2(const T&& param); //param也是一个右值引用

如果你向f1和f2传左值,会都是会直接报错的。

除此之外,像下面这种情况也不是万能引用:

template<class T, class Allocator=allocator<T>>
class vector{
public:
void push_back(T&& x);
};

push_back的形参x的型别T是受vector影响的,假设给定T为Widget,那么就会被实例化为如下代码:

template<class Widget, class Allocator=allocator<T>>
class vector{
public:
void push_back(Widget&& x);
};

而作为对比,vector类中的emplace_back函数则是一个实实在在的万能引用:

template<class T, class Allocator=allocator<T>>
class vector{
public:
template<class...Args>
void emplace_back(Args&&.. args);
};

args的型别Args完全独立于vector的型别形参T,Args必须在每次调用emplace_back时被推导,因此args是万能引用。

为什么是“万能引用”?

你一定非常好奇,为什么这种形态被称作“万能引用”。原因正像前文所说的,通过“万能引用”,对某型别的引用T&&,既可以表达右值引用,也可以表达左值引用。

比如说,传入一个右值引用,一般都要给传入的参数加一个std::move操作确保变量的可移动性:

class Widget
{
public:
Widget(Widget&& rhs):name(std::move(rhs.name)){}
private:
std::string name;
};

而万能引用则有所不同,它一般是通过std::forward来进行转换:

class Widget
{
public:
template<typename T>
void setName(T&& newName)
{name = std::forward<T>(newName);}
private:
std::string name;
};

在这种情况下,若newName是一个左值引用,则forward函数不会对它进行操作,它的返回值仍然是一个左值引用;若newName是一个右值,则会进行std::move的转换。用户使用时无需区分,这也正是它“万能”之处。

举个例子:

Widget w;
std::string c = "123";
w.setName(w); //传入是一个左值,forward返回左值引用
w.setName("123"); //传入是一个右值,forward返回右值引用
w.setName(std::move(w)); //传入是一个右值,forward返回右值引用

引用折叠

你一定会很奇怪,为什么万能引用的形式明明是T&&,却既可以代表左值又可以代表右值。这就要涉及到C++的引用折叠语法了。

首先,C++不支持“引用的引用”这种概念,这样的代码在C++中是非法的:

int x;
auto& & rx = x;

但是,假设我们向前面的万能引用函数f传入一个左值引用:

Widget w = w;
f(w); //T的推导型别为Widget&

那么实例化的结果如下:

void f(Widget& && param);

之所以这样的代码能通过,是因为在特殊的情况下(比如模板实例化),C++应用了引用折叠的语法。

有左值和右值两种引用,所以就有四种可能的组合:左值-左值、左值-右值、右值-左值、右值-右值,如果引用的引用出现在允许的语境,改双重引用会被折叠成单个引用:

如果任一引用为左值引用,则结果为左值引用。否则(即两个都为右值引用),结果为右值引用。

因此上述例子中,最终将param推导为左值引用。

此外,auto的型别推导也会应用引用折叠的场景,例如:

Widget w;
auto &&w1 =w; //w1是个左值

话说到这里,我们就可以更深入地理解万能引用,其实它就是满足了下面两个条件的语境中的右值引用:

1.型别推导的过程会区别左值和右值。T型别的左值推导结果为T&,而T型别的右值推导结果为T。

2.会发生引用折叠。

C++的万能引用解析的更多相关文章

  1. golang包引用解析

    golang包引用解析 环境变量配置如下: GOROOT----[C:\Go] GOPATH----[F:\workspace\go_home] vs code配置如下: F:\workspace\g ...

  2. c++11-17 模板核心知识(十)—— 区分万能引用(universal references)和右值引用

    引子 如何区分 模板参数 const disqualify universal reference auto声明 引子 T&&在代码里并不总是右值引用: void f(Widget&a ...

  3. [ Javascript ] 内存泄露以及循环引用解析

    内存泄露 在javascript中,我们非常少去关注内存的管理. 我们创建变量,使用变量,浏览器关注这些底层的细节都显得非常正常. 可是当应用程序变得越来越复杂而且ajax化之后,或者用户在一个页面停 ...

  4. 万能正则解析 json 数据 解析成键值对

    string txt = "{\"ip\": \"127.0.0.1\", \"port\": 80, \"status ...

  5. 用c#写一个json的万能解析器

    CommonJsonModel .cs /// <summary> /// 万能JSON解析器 /// </summary> public class CommonJsonMo ...

  6. C++11(列表初始化+变量类型推导+类型转换+左右值概念、引用+完美转发和万能应用+定位new+可变参数模板+emplace接口)

    列表初始化 用法 在C++98中,{}只能够对数组元素进行统一的列表初始化,但是对应自定义类型,无法使用{}进行初始化,如下所示: // 数组类型 int arr1[] = { 1,2,3,4 }; ...

  7. Spring框架之spring-web web源码完全解析

    Spring框架之spring-web web源码完全解析 spring-web是Spring webMVC的基础,由http.remoting.web三部分组成,核心为web模块.http模块封装了 ...

  8. AFN解析器里的坑

    AFN框架是用来用来发送网络请求的,它的好处是可以自动给你解析JSON数据,还可以发送带参数的请求AFN框架还可以监测当前的网络状态,还支持HTTPS请求,分别对用的类为AFNetworkReacha ...

  9. 在.NET2.0中解析Json和Xml

    在.NET解析json有很多方法,这里介绍最简单也用的最多的一种. 一.添加引用 解析Json,先下载开源控件 Newtonsoft.Json.dll 下载地址:http://files.cnblog ...

  10. c#解析Josn(解析多个子集,数据,可解析无限级json)

    首先引用 解析类库 using System; using System.Collections.Generic; using System.Linq; using System.Text; name ...

随机推荐

  1. 远程ubuntu虚拟机(VirtualBox)

    环境 实机win10,虚拟软件是Oracle VM VirtualBox 下载地址https://www.virtualbox.org/ ubuntu虚拟机配置 网络选桥接网卡, 原因是桥接网卡下,根 ...

  2. centos7 添加自定义程序为系统服务

    centos6版本的系统服务是/etc/init.d启动脚本的方式,centos7采用强大的systemctl来管理系统服务,大幅提供了系统服务的运行效率,但是服务的配置和以前版本完全不同,这是很大的 ...

  3. Java-面向对象基础 构造方法

    public class Dog {// 定义属性 String nick; String color; int age; // 定义构造方法 public Dog(String nick,Strin ...

  4. ASP.NET Core中使用日志组件NLog

    上一篇文章描述了如何在ASP.NET Core中使用Log4Net记录日志.本篇将使用另外一个组件NLog在ASP.NET Core中记录日志. 1.引入程序集 NLog.Web.AspNetCore ...

  5. C++ 手动实现单向循环链表(课后作业版)

    单向循环链表,并实现增删查改等功能 首先定义节点类,类成员包含当前节点的值, 指向下一个节点的指针 循环链表的尾节点指向头节点 节点定义: //node definition template < ...

  6. 《Unix/Linux系统编程》第十四章学习笔记 20201209戴骏

    MySQL数据库系统 知识点总结 一.MySQL MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,目前属于Oracle 旗下产品.MySQL 是最流行的关系型数据库管理系统之一 ...

  7. vue v-model的原理

    关于v-model v-model 可以实现表单与data里的双向绑定 很多插件上可以在组件上使用v-model,他是如何实现的呢,其实v-model就是一个语法糖 <input v-model ...

  8. ubuntu18.04.3新装系统安装QT5.14.1和环境配置

    第一步:下载QT: http://download.qt.io/archive/qt/ 或者 https://mirrors.tuna.tsinghua.edu.cn/qt/archive/qt/ 下 ...

  9. calibredrv命令

    flattencell: set L1 [layout create *.gds -dt_expand] $L1 flatten cell TOP_CELL_NAME $L1 gdsout ./*_f ...

  10. 数据库tempdb的事物日志已满,原因为“ACTIVE_TRANSACTION”

    系统运行过程中,突然报错数据库tempdb的事物日志已满,原因为"ACTIVE_TRANSACTION".导致所有业务崩溃. 接到报警后,进入数据库服务器.检查硬盘空间正常,于是登 ...