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++知识库 -> 右值引用、std::move、std::forward -> 正文阅读

[C++知识库]右值引用、std::move、std::forward

左值和右值

能取地址、有名字的是左值。不能取地址、没有名字的是右值。

比如对于&(b + c);这样的表达式就不能通过编译,因为b+c是右值,不能取地址。

C++11中又将右值进一步区分为纯右值将亡值

  • 纯右值:临时变量(比如a = b + c中的b + c)和字面值(比如a = 2中的2)
  • 将亡值: 1)返回右值引用的函数的调用表达式; 2)转换为右值引用的转换函数的调用表达式

右值引用是左值还是右值?

有名字的右值引用是左值,没有名字的右值引用是右值。

// 形参是个右值引用
void change(int&& right_value) {
    right_value = 8;
}
 
int main() {
    int a = 5; // a是个左值
    int &ref_a_left = a; // ref_a_left是个左值引用
    int &&ref_a_right = std::move(a); // ref_a_right是个右值引用
 
    change(a); // 编译不过,a是左值,change参数要求右值
    change(ref_a_left); // 编译不过,左值引用ref_a_left本身也是个左值
    change(ref_a_right); // 编译不过,右值引用ref_a_right本身也是个左值
     
    change(std::move(a)); // 编译通过
    change(std::move(ref_a_right)); // 编译通过
    change(std::move(ref_a_left)); // 编译通过
 
    change(5); // 当然可以直接接右值,编译通过
     
    cout << &a << ' ';
    cout << &ref_a_left << ' ';
    cout << &ref_a_right;
    // 打印这三个左值的地址,都是一样的
}

// 以上代码来源于 https://zhuanlan.zhihu.com/p/335994370

或者说,作为函数返回值的 && 是右值,直接声明出来的 && 是左值

std::move的实现

std::move的原型是:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);

首先要关注的是,move的形参T会被编译器当做什么类型。还要先关注一个概念,引用折叠

引用折叠:

  • X& &X&& &X& &&都折叠成X&,用于处理左值。左值引用的左值引用、右值引用的左值引用、左值引用的右值引用都是左值引用。总结起来就是,只要有左值引用就会被折叠成左值引用。
  • X&& &&折叠成X&&,用于处理右值。也就是说,右值引用的右值引用是右值引用。

在了解了引用折叠的概念之后,就能理解T会被推断为什么类型了。首先有一点要保证的是,T&&一定被推断成某种引用。

  • 如果传入的实参是左值,那么T&&应该是一个左值引用。根据引用折叠的规则,只有T是一个左值引用时,T&&才会是一个左值引用。
  • 如果传入的实参是右值,那么T&&应该是一个右值引用。那么,T应该被推断为非引用类型。

然后就是调用模板类remove_reference<T>了。这个类是通过模板泛化和特化实现的:

template <typename T> struct remove_reference{
    typedef T type;  //定义T的类型别名为type
};
 
//部分版本特例化,将用于左值引用和右值引用
template <class T> struct remove_reference<T&> //左值引用
{ typedef T type; }
 
template <class T> struct remove_reference<T&&> //右值引用
{ typedef T type; }   
  
//举例如下,下列定义的a、b、c三个变量都是int类型
int i;
remove_refrence<decltype(42)>::type a;             //使用原版本,
remove_refrence<decltype(i)>::type  b;             //左值引用特例版本
remove_refrence<decltype(std::move(i))>::type  b;  //右值引用特例版本 

于是remove_reference这个类的作用是,如果传入的是T类型的左值引用或者右值引用,那么type成员可以萃取出T的类型(两个偏特化版本);如果传入的不是引用,也返回T的类型(泛化版本)。

再看下面的函数:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
	return static_cast<typename remove_reference<T>::type&&>(t);

先推断出T的类型,然后通过remove_reference<T>::type萃取出非引用的类型,使用static_cast将其转化为右值引用类型。

最终,std::move实现了把左值、右值、左值引用、右值引用都转化为右值引用的功能。

std::forward的作用

第一个问题是,std::forward是为了解决什么问题的?

std::forward主要是配合万能引用的:

#include <iostream>
#include <stdexcept>

using namespace std;

class A {
public:
    A(int&& n) {
        cout << "A : rvalue chosen!" << endl;
    }
    A(int& n) {
        cout << "A : lvalue chosen!" << endl;
    }
};

template<class T>
void print(T&& t) {
    A a(t);
}

int main()
{
    int n = 2;
    print(n);
    print(2);
    return 0;
}

print函数的参数T&&就是万能引用,不管实参是左值、右值、左值引用还是右值引用,它都能匹配。上面的函数运行的结果是:

A : lvalue chosen!
A : lvalue chosen!

第二次参数明明是一个右值,但是在print函数中,右值引用t本身是一个左值,于是传递给A的构造函数,调用了左值引用的版本。

std::forward就是用来解决这种情况:如果我们想要调用A的右值引用版本的构造函数,也就是说保持t的右值引用的特性。这时候就需要使用std::forward

A a(std::forward<T>(t));

std::forward的实现

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

std::forward虽然是一个函数模板,但是它在使用的时候需要显式指定模板参数。就像上面的A a(std::forward<T>(t));。如果T的类型是右值引用,那么会匹配到第二个版本。因为remove_reference::value能萃取出_Tp本身的类型,于是第一个版本的参数是左值引用类型,第二个版本的参数是右值引用类型。
在匹配到第二个版本后,static_cast__t转换为_Tp&&类型。由引用折叠可知,右值引用的右值引用仍然是右值引用,于是就返回了一个右值引用。这个右值引用是没有名字的,于是它本身是右值

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-03 11:43:10  更:2021-09-03 11:43:56 
 
开发: 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年12日历 -2024/12/27 20:25:34-

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