IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 万能引用和完美转发 -> 正文阅读

[C++知识库]万能引用和完美转发

1.前置知识:

????????在了解万能引用和完美转发之前需要了解左值,右值,左值引用,右值引用和引用折叠。下面简单概括一下这些名词,关于每个名词的详细解释网上有很多非常好的文章,故不在本文赘述。

1.1 左值和右值:

????????简单的说可以取地址的就是左值,如变量。反之就是右值,如常数。仅通过是否能出现在等号左边并不能完全区分左值,如"a=b"这种。此外有些类禁用了'='操作符,然而它们仍是左值。

1.2 左值引用和右值引用:

????????右值引用是C++11提出的,因此之前的引用均为左值引用。C++11采用“&&”操作符来进行右值引用,如“int && a=10”。绑定到右值以后右值的生存期会延长至与绑定到它的右值引用的生存期。

1.3 引用折叠:

????????引用折叠简单的说就是多个引用叠加的规则,C++不允许对引用复引用,因此当多个引用叠加具有一下规则:

& + & -> &
& + && -> &
&& + & -> &
&& + && -> &&

进而可以得出多个引用叠加只要有一个左值引用,那么最终的结果就是左值引用。

2.万能引用:

????????万能引用是一种写法,使得一个函数可以同时接受左值引用或右值引用为参数。

2.1 万能引用之前:

? ? ? ? 在引入万能引用之前,我们写接收引用的函数需要如下代码:

void func(int& a) {
    std::cout << "left" << '\n';
}

void func(int&& a) {
    std::cout << "right" << '\n';
}

? ? ? ? 我们需要写两个函数,以便可以使用func(a)和func(1)。而万能引用的存在使得仅需写一个函数即可同时支持这两种写法。这也是为什么需要万能引用。

2.2 万能引用:

? ? ? ? 上面的函数进行以下写法即为万能引用:

template<typename T>
void func1(T&& a)
{
    
}

? ? ? ? 为了验证这个函数是否能够接收左值和右值我们可以测试一下:

#include <iostream>

void func(int& a) {
    std::cout << "left" << '\n';
}

void func(int&& a) {
    std::cout << "right" << '\n';
}

template<typename T>
void func1(T&& a)
{
    func(a);
}

int main()
{
    int a = 3;
    func1(3);
    func1(a);

    return 0;
}

? ? ? ? 结果如下:

a4ae9806760b47f4b9e8597b6752df72.png

? ? ? ? ?可见这左值和右值都能够成功接收。而为什么输入了一个右值和一个左值,结果却都调用了左值的函数,这便是为什么要有完美转发。简单的说就是func1里面将a作为左值传递给了func,因此输出的都是左值。解决方式可以在第三章完美转发查看,本章先实现万能引用。

2.3 万能引用原理:

? ? ? ? 万能引用原理就是因为引用折叠,以上述代码为例,当调用func1(3)时,由于接受的是常数,因此模板类型T被推导为int,因此func1等价于void func1(int&& a),可以接收右值引用。当调用func1(a)时,由于a是左值,因此T被推导为int&,函数等价于void func1(int& && a)。由于1.3的引用折叠得知函数等价于void func1(int& a),可以接收左值引用。

2.4 万能引用的注意事项:

2.4.1 只有T被推导时才是万能引用:

? ? ? ? 由于2.3不难得出万能引用的实现原理是类型推导,因此非模板函数或者实例化时无需推导则无法成为万能引用。下面例子可以看出

//func和func1仍与2.2相同

int main()
{
    int a=3;
    func1(a);//正确
    func1(3);//正确
    func1<int>(a);//编译报错
    return 0;
}

? ? ? ? func1<int>(a)错误的原因很简单,在a传递前模板类型T已经被实例化为void func1(int && a),成为了只能接收右值引用的函数,而a是左值,因此毫无疑问会产生报错。

2.4.2 看到&&注意万能引用:

? ? ? ? 根据上面可以看出C++11及其之后标准的代码中看到&&修饰的参数并不一定是右值引用,同样的有T&&的万能引用也要在实例化时候注意不要让它变成右值引用。如果不注意前者有可能在写重载函数时出现问题,下面举一个简单例子

#include <iostream>

template<typename T>
void func(T& a) {
    std::cout << "left" << '\n';
}

template<typename T>
void func(T&& a) {
    std::cout << "right" << '\n';
}

int main()
{
    int a = 3;
    func(3);//编译通过
    func(a);//编译报错,因为上述重载的两个函数均可以接收左值
    func<int>(a);
    //编译通过,在2.4.1中得出此时第二个func由万能引用变为仅接收右值引用的函数,不会产生歧义

    return 0;
}

? ? ? ? 在此例子,本身只是想通过模板类实现func,但是由于万能引用的存在,使得func(a)时产生歧义。在复杂的程序中,这种错误并不一定会被很容易的发现且可能产生其它问题。

3.完美转发:

? ? ? ? 在2.2中我们已经发现了当函数接收右值时也会变成左值,这是因为任何的函数内部,对形参的直接使用,都是按照左值进行的。因此func1(a)传递的一定是左值。为了解决这个问题引用了完美转发。

3.1 完美转发的使用:

????????完美转发使用std::forward函数,以修改后2.2的程序为例

#include <iostream>

void func(int& a) {
    std::cout << "left" << '\n';
}

void func(int&& a) {
    std::cout << "right" << '\n';
}

template<typename T>
void func1(T&& a)
{
    func(std::forward<T>(a));
}

int main()
{
    int a = 3;
    func1(3);
    func1(a);

    return 0;
}

? ? ? ? 结果如下:

c1bc1dfa18b040adadddc365bfeeea5c.png

? ? ? ? 可以看到成功将’3‘以右值形式传给了func。至于可不可以使用std::forward<int>(a)我们在3.2中分析完源码即可分析。

3.2 forward源码分析:

3.2.1 源码简化

????????forward源码如下

  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type& __t) noexcept
    { return static_cast<_Tp&&>(__t); }

  template<typename _Tp>
    constexpr _Tp&&
    forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
    {
      static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
		    " substituting _Tp is an lvalue reference type");
      return static_cast<_Tp&&>(__t);
    }

? ? ? ? 核心内容简化伪代码如下

  template<typename _Tp>
  _Tp&& forward(_Tp& __t)
    { return static_cast<_Tp&&>(__t); }

  template<typename _Tp>
   _Tp&& forward(_Tp&& __t) 
    {
      return static_cast<_Tp&&>(__t);
    }

3.2.2 正确设置forward的模板参数? ? ? ??

? ? ? ? 首先重点强调以下forward(a)这种未传递模板参数的用法会报错。因为任何的函数内部,对形参的直接使用,都是按照左值进行的。因此a无论是什么,在使用时始终是左值,因此失去了完美转发的意义。所以正确使用必须得使用下列写法:

template<typename T>
void func1(T&& a)
{
    func(std::forward<T>(a));
}

? ? ? ? 此时T是a的原始类型,forward里面的参数a为左值引用。forward函数就是通过T来推导的。

3.2.3 正确设置模板参数的情况

? ? ? ? 正确设置模板参数后将会出现以下情况:

? ? ? ? a.原始类型为左值引用,传入参数为左值引用。

? ? ? ? b.原始类型为左值引用,传入参数为右值引用。

? ? ? ? c.原始类型为右值引用,传入参数为左值引用。

? ? ? ? d.原始类型为右值引用,传入参数为右值引用。

? ? ? ? 但是实际上分析,这两个forward函数的返回值仅由_Tp决定,因此我们只需要关注传入的T的属性是左值引用还是右值引用。而且根据引用折叠传入的属性一定和返回的属性一致。因此完美转发除了返回正确的属性外,我们也可以传递指定的_Tp来传递想要的属性。如forward<int>(a),这样返回的一定是右值。不过move也有强制右值的作用。因此还是尽量传递原始属性来实现这个函数特有的意义。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-31 23:45:54  更:2022-03-31 23:46:16 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:38:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码