指针的指针 ok, 引用的引用 no ---- 理解引用折叠

这篇具有很好参考价值的文章主要介绍了指针的指针 ok, 引用的引用 no ---- 理解引用折叠。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

       我们都知道,在C/C++中,出现指针的指针,也就是二级指针的场景是合法的,甚至可以是更多级的指针,都是ok的;但是如果出现了引用的引用,那绝对是非法操作,任何一款C/C++的编译器都很乐意为您检测出此类非法操作。既然会讨论这个问题,说明 引用的引用 这样的场景是肯定会出现的,尤其是C++11标准以后;那该怎么办呢? 所以就顺势而为的出现了 引用折叠 的技术,顾名思义,这一技术就是将双重引用折叠成单个引用

int x;
auto& &rx = x;    //error. 不可以声明引用的引用

以上代码就是出现引用的引用的场景,胆敢写出这样的代码,编译器肯定会报错有问题的。

在之前文章探讨了有关模板函数类型推导以及auto类型推导之后,对下面代码中的推导结果便不再心存疑问了;

//默认下面的 Widget 为一个类

Widget createWidgetObj();    //返回Widget类型的右值;

Widget w;    //定义一个Widget类型的对象,左值;


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


func(w);    // 实参为左值  T 的推导结果为 Wdiget&

func(createWidgetObj());    // 实参为右值  T 的推导结果为非引用类型的 Widget

在以上代码中,func(w) 的调用推导出T为 Widget& 类型;那如果就以此类型实例化该模板函数func会得到下面的结果:

void func(Widget& &&param);

这不就是引用的引用吗?为什么编译器没有报错而且还得出了正确的推导结果(有点只许州官放火,不许百姓点灯的意思哦)!答案就是此处运用了引用折叠技术;凡是出现引用的引用的场景中,都会使用该技术,才得以保障它们顺利的运行下去;包括 std::forward() 的实现,其背后的关键也是引用折叠技术(后面会详细介绍)。

自C11之后出现了右值引用,加上之前的左值引用,所以就有了四种引用--引用的组合方式(左值引用--右值引用,左值引用--左值引用,右值引用--左值引用,右值引用--右值引用);如果以上这些引用的引用出现在了特殊的允许的语境中(如模板实例化,auto变量类型推导),该双重引用就会折叠成单个引用,具体规则如下:

如果双重引用中有任一引用为左值引用,则最终结果为左值引用;

否则(即双重引用均为右值引用时),最终结果为右值引用;

std::forward 的实现细节

我们都知道针对万能引用需要实施 std::forward,才能够保证原本实参为左值或右值属性不变的特性,尤其是需要将该参数转发给其他函数进行调用时,这点尤为重要。看下面一段代码:

template<typename T>
void func(T&& fparam)
{
    someFunc(std::forward<T>(fparam));    // 将形参 fparam 转发给 someFunc 函数使用
}

关于将参数转发给其他函数使用时,如何保证参数的左值右值属性信息不变的这一点,std::forward 是如何做到的呢?下面详细展开介绍。先给出一段简化的 std::forward 的实现源代码:

template<typename T>
T&& forward(typename remove_reference<T>::type& param)
{
    return static_cast<T&&>(param);
}

实参为左值时

假设调用模板函数 func 时,实参为左值 Widget,则 T 被推导为 Widget& 类型;则对std::forward的实例化调用就变成了 std::forward<Widget&>(param),将Widget&代入forward的源码中会得到如下代码:

Widget& && forward(typename remove_reference<Widget&>::type& param)
{
    return static_case<Widget& &&>(param);
}

类型特征 typename remove_reference<Widget&>::type 产生的结果就是 Widget 类型,进而会有如下代码:

Widget& && forward(Widget& param)
{
    return static_cast<Widget& &&>(param);
}

引用折叠技术在以上代码中应用了两处地方,一处是函数返回值类型,一处是强制类型转换;经过引用折叠之后得到最终的 forward 代码,如下:

Widget& forward(Widget& param)
{
    return static_cast<Widget&>(param);
}

正如所料,std::forward 接受一个左值引用,最终返回一个左值引用,内部的强制类型转换并未做任何事情,因为形参类型本身就已经是左值引用了;根据定义,返回左值引用即就是一个左值;所以从最开始调用模板函数 func 时,传入一个左值实参时,经 std::forward 返回一个左值 转发给 someFunc 函数,保持了实参左值属性的不变

实参为右值时

当以 Widget 类型的右值实参传给模板函数 func 时,T 被推导为非引用类型的 Widget,于是 std::forward 的源码就有了如下的变化:

Widget&& forward(typename remove_reference<Widget>::type& param)
{
    return static_cast<Widget&&>(param);
}

针对非引用类型Widget实施 remove_reference 操作的结果就和原类型 Widget 一样,所以 std::forward 就有了如下的变化:

Widget&& forward(Widget& param)
{
    return static_cast<Widget&&>(param);
}

注意:以上代码并没有发生引用折叠。也就是说,当实参以 Widget 类型的右值 传入模板函数 func 时,模板函数的形参 fparam 的类型被推导为 Widget 类型的右值引用Widget&&,但 fparam 本身是个左值,再经过 std::forward 的处理(std::forward 接受了一个 Widget&& 类型的左值 fparam,返回了一个Widget&& 类型的右值),将其强制转换为右值转发给someFunc函数,保持了最开始实参为右值的不变性

以上就是 std::forward 完美转发语义的实现,完美的地方就在于保证了实参左、右值属性的不变性;并将其转发给其他函数使用。

在C++14中出现了 模板别名 template alias  

template<typename T>
	using remove_reference_t = typename remove_reference<T>::type;

所以 std::forward 的源码会变得更简洁一些:

template <typename T>
T&& forward(remove_reference_t<T>& param)
{
    return static_cast<T&&>(param);
}

引用折叠出现的几种语境

引用折叠会出现的语境有四种。模板实例化以及auto变量的类型推导,其实这两种在本质上是一模一样的。在如下代码中,auto也能够模仿之前出现的代码

auto&& w1 = w;    // w 为 Widget 类型的左值,所以 auto ==> Widget&

Widget& && w1 = w;    // 出现引用的引用,经过引用折叠

Widget& w1 = w;    // w1 为左值引用


auto&& w2 = createWidget();    // 以Widget类型的右值初始化 w2; auto ==> Widget

Widget&& w2 = createWidget();    // 此处并没有引用的引用,w2 的类型为右值引用

第三种语境就是出现在 typedef 的类型别名声明中;假如原本想要在一个模板类中内置一个右值引用别名声明,如下:

template<typename T>
class Widget
{
public:
    typedef T&& RvalueRefT;
...
...
}

如果以任意类型左值引用来实例化该模板类,则原本想声明的右值引用类型就变成了左值引用了,有点名不符实了;假如以 int& 来实例化该模板类,则有如下代码:

Widget<int&> w;

typedef int& && RvalueRefT;    // 引用的引用,应用引用折叠技术

typedef int& RvalueRefT;  // RvalueRefT 实际变成了左值引用,并不是原本想要的右值引用

最后一种会发生引用折叠的场景就是 decltype 的运用中,如果在分析一个涉及 decltype 类型的过程中出现了引用的引用,则会应用引用折叠技术而处理掉。文章来源地址https://www.toymoban.com/news/detail-468984.html

到了这里,关于指针的指针 ok, 引用的引用 no ---- 理解引用折叠的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 关于GPT4,我们都知道什么?

    人工智能原理与实践 全面涵盖人工智能和数据科学各个重要体系经典 北大出版社,人工智能原理与实践 人工智能和数据科学从入门到精通 详解机器学习深度学习算法原理 我们生活在一个AI激动人心的时代,你会不停看到各种新型模型的推出,它们彻底改变了 AI 领域。2022

    2023年04月16日
    浏览(39)
  • 我们所知道的关于 OpenAI 的 ChatGPT 的一切

    如果您还没有听说过ChatGPT,这是来自人工智能实验室 OpenAI 的不可思议的新聊天机器人,这里是您需要了解的有关这个有争议的新程序的所有信息的快速入门。 ChatGPT 是一种人工智能工具,允许用户生成原始文本。你可以问它问题,给它创造性的提示,并用它来生成一大堆不

    2023年04月13日
    浏览(41)
  • C++【4】指针与引用;数组指针;指针数组

    /*     指针变量作为函数参数         函数的参数可以是指针类型,它的作用是将一个变量的地址传送到另一个函数中。         指针变量作为函数参数与变量本身作为函数参数不同,变量作函数参数传递的是具体值。         而指针作为函数参数传递的是内存的地址

    2024年02月07日
    浏览(35)
  • JDK 22 和 JDK 23:到目前为止我们所知道的

    Oracle Java 平台组首席架构师Mark Reinhold宣布, JDK 22是自JDK 21以来的第一个非 LTS 版本,现已进入第二个候选版本阶段。主线源代码存储库于 2023 年 12 月中旬(Rampdown 第一阶段)分叉到 JDK稳定存储库,定义了 JDK 22 的功能集。可以解决诸如回归或严重功能问题之类的关键错误,

    2024年03月22日
    浏览(39)
  • 【C++】C++ 引用详解 ⑦ ( 指针的引用 )

    指针的引用 效果 等同于 二级指针 , 因此这里先介绍 二级指针 ; 使用 二级指针 作为参数 , 可以实现如下功能 : 动态内存管理 : 借助二级指针 , 可以在函数中分配或释放内存 ; 如 : 创建一个动态数组或调整现有数组的大小 , 在函数中需要一个指向指针的指针作为参数 , 以便修

    2024年02月11日
    浏览(37)
  • C语言指针操作(三)通过指针引用数组

    通过指针引用数组的几种方法的原理和差异;以及利用指针引用数组元素的技巧 关于地址,指针,指针变量可以参考这篇文章: C语言指针操作(一)地址,指针,指针变量是什么 关于指针变量作为函数参数可以参考这篇文章: C语言指针操作(二)指针变量作为函数参数

    2024年02月04日
    浏览(48)
  • C语言指针操作(五)通过指针引用多维数组

    多维数组的地址,通过指针引用多维数组详解。 通过指针引用一维数组可以参考这篇文章: C语言指针操作(三)通过指针引用数组 目录 一、多维数组的地址 1.1引入 1.2地址举例说明 1.3地址类型详解 1.4实例说明 二、指向多维数组元素的指针变量 2.1指向数组元素的指针变量

    2024年02月03日
    浏览(45)
  • C语言指针操作(七)通过指针引用字符串

    通过指针引用字符串详解,以及字符指针变量和字符数组的比较、 在平常的案例中已大量地使用了字符串,如在 printf函数中输出一个字符串。这些字符串都是以直接形式 (字面形式) 给出的,在一对双引号中包含若干个合法的字符。在本节中将介绍使用字符串的更加灵活方便

    2024年02月03日
    浏览(51)
  • C++的指针和引用

    C++中内存单元内容和地址 内存由很多的内存单元组成,这些内存单元用于存放各种类型数据; 计算机对内存的每个内存单元都进行了编号,这个编号就称为内存地址,地址决定了内存单元在内存中的位置; 记住这些内存单元地址不方便,因此C++语言的编译器让我们通过名字

    2024年02月06日
    浏览(46)
  • c++的引用和指针

    我们要清楚的知道,使用指针和引用都可以的传入函数的main函数的变量在局部函数改变值时,main函数里面相应的变量也会改变值。但他俩的方式不同。 我们先来说指针,指针传入局部参数时,他会在创建个局部指针变量,然后把传入的地址赋值给局部的指针变量,然后修改

    2024年02月09日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包