返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >C++右值引用,移动语义与完美转发得方法
  • 434
分享到

C++右值引用,移动语义与完美转发得方法

2023-06-29 15:06:41 434人浏览 泡泡鱼
摘要

本篇内容主要讲解“c++右值引用,移动语义与完美转发得方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++右值引用,移动语义与完美转发得方法”吧!C++—&mda

本篇内容主要讲解“c++右值引用,移动语义与完美转发得方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++右值引用,移动语义与完美转发得方法”吧!

    C++——左值与右值、右值引用、移动语义与完美转发

    在C++或者C语言中,一个表达式(可以是字面量、变量、对象、函数的返回值等)根据其使用场景不同,分为左值表达式和右值表达式。

    一、左值和右值的定义

    左值的英文为locator value,简写为lvalue,可意为存储在内存中、有明确存储地址(可寻址)的数据

    右值的英文为read value,简写为rvalue,指的是那些可以提供数据值的数据(不一定可寻址,例如存储与寄存器中的数据)

    二、如何判断一个表达式是左值还是右值(大多数场景)

    可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。例如:

    int a = 5;

    其中,变量a就是一个左值,而字面量5就是一个右值。

    注:C++中的左值可以当作右值使用,反之则不行,如

    int b = 10; //b是一个左值a = b; //a、b都是左值,只不过b可以当作右值使用10 = b; //错误,10是一个右值,不能当作左值使用

    有名称的、可以获取到存储地址的表达式即为左值;反之则为右值

    以上面定义的变量a、b为例,a和b是变量名,则通过&a和&b可以获得他们的存储地址,因此a和b都是左值;反之,字面量5、10,它们既没有名称,也无法获取其存储地址(字面量通常存储在寄存器中,或者和代码存储在一起),因此5、10都是右值

    三、C++右值引用

    C++ 98/03标准中就有引用,但这里的引用只能操作左值,无法对右值添加引用,故被称为左值引用,例如:

    int num = 10;int &b = num; //正确int &c = 10; //错误

    注意,虽然C++ 98/03标准不支持为右值建立非常量左值引用,但允许使用常量左值引用操作右值。即,常量左值引用既可以操作左值,也可以操作右值,例如:

    int num = 10;const int &b = num; //正确const int &c = 10; //正确

    为什么需要右值引用?

    右值往往是没有名称的,因此要使用它只能借助引用的方式。这就产生一个问题,实际开发中我们可能需要对右值进行修改(如实现移动语义时),而是用常量左值引用的方式是无法做到这一点的。

    为此,C++11新标准引入了另一种引用方式,成为右值引用,用&&表示

    需要注意的是,和声明左值引用一样,右值引用也必须立即进行初始化操作,且只能使用右值进行初始化,比如:

    int num = 10;int &&a = num; //错误,右值引用不能初始化为左值int &&a = 10; //正确

    和常量左值引用不同的是,右值引用还可以对右值进行修改。例如:

    int &&a = 10;a = 100; //正确

    另外,C++语法上是支持定义常量右值引用的,例如:

    const int&& a = 10;

    但这种定义出来的右值引用并无实际用处。一方面,右值引用主要用于移动语义和完美转发,其中前者需要有修改右值的权限;其次,常量右值引用的作用就是引用一个不可修改的右值,而这项工作常量左值引用就可以完成

    四、std::move()与移动语义

    C++11标准中,借助右值引用可以为指定类添加移动构造函数,这样当使用该类的右值对象(可以理解为临时对象)初始化同类对象时,编译器会优先选择移动构造函数。

    注:移动构造函数的调用时机是:用同类的右值对象初始化新对象。那么当用此类的左值对象(有名称,能获取其存储地址的实例对象)初始化同类对象时,如何调用移动构造函数呢?C++11给出的解决方案就是调用std::move()函数。

    虽然move是移动的意思,但是该函数并不移动任何数据,它的功能是将某个左值强制转化为右值,常用于实现移动语义

    用法示例:

    #include<iOStream>#include<utility>using namespace std;class movedemo{public:    movedemo():num(new int(0)) {        cout << "construct!" << endl;    }    //copy constructor    movedemo(const movedemo &d):num(new int(*d.num)) {        cout << "copy constrct!" << endl;    }    //move constructor    movedemo(movedemo &&d):num(d.num) {        d.num = NULL;        cout << "move construct!" << endl;    }private:    int *num;};int main() {    movedemo demo;    cout << "demo2:\n";    movedemo demo2 = demo;    cout << "demo3:\n";    movedemo demo3 = std::move(demo);//执行完之后demo.num会置为空    return 0;}

    程序运行结果:

    construct!demo2:copy constrct!demo3:move construct!

    通过观察程序的输出结果,以及对比 demo2 和 demo3 初始化操作不难得知,demo 对象作为左值,直接用于初始化 demo2 对象,其底层调用的是拷贝构造函数;而通过调用 move() 函数可以得到 demo 对象的右值形式,用其初始化 demo3 对象,编译器会优先调用移动构造函数。

    五、 完美转发

    什么是完美转发?

    它指的是函数模板可以将自己的参数“完美”地转发给内部调用的其他函数。所谓完美,即不仅能准确地转发参数的值,还能保证被转发参数的左、右值属性不变

    举个栗子:

    template<typename T>void function(T t) {otherdef(t);}

    如上所示,function()函数模板中调用了otherdef()函数。在此基础上,完美转发指的是:如果function()函数接收到的参数t为左值,那么该函数传递给otherdef()的参数t也是左值;反之如果function()函数接收到的参数t为右值,那么传递给otherdef()函数的参数t也必须为右值。

    显然,上面这个例子中,function()函数模板并没有实现完美转发。一方面,参数t为非引用类型,这意味着在调用function()函数时,实参将值传递给形参的过程就需要额外进行一次拷贝操作;另一方面,无论调用function()函数模板时传递给参数t的是左值还是右值,对于函数内部的参数t来说,它有自己的名称,也可以获取它的存储地址,因此它永远都是左值,即传递给otherdef()函数的参数t永远都是左值。总之,无论从哪个角度看,function()函数的定义都不“完美”。

    为什么需要完美转发?

    C++11新标准中引入了右值引用和移动语义,因此很多场景中是否实现完美转发,直接决定了该参数的传递过程使用的是拷贝语义(调用拷贝构造函数)还是移动语义(调用移动构造函数)

    如何实现完美转发?

    C++98/03标准:由于没有右值引用,只能通过重载函数模板(可通过常量左值引用传递右值)的方式实现转发,且这种方式存在弊端,如:此实现方式只适用于模板函数仅有少量参数的情况,否则就需要编写大量的重载函数模板,造成代码的冗余。

    为了方便用户更快速地实现完美转发,C++11标准中允许在函数模板中使用右值引用来实现完美转发

    C++11标准中规定,通常情况下右值引用形式的参数只能接收右值,不能接收左值。但对于函数模板中使用右值引用语法定义的参数来说,它不再遵守这一规定,既可以接收右值,也可以接收左值(此时的右值引用又被称为“万能引用”)

    仍以function()函数为例,在C++11标准中实现完美转发,只需编写如下一个模板函数即可:

    template<typename T>void function(T &&t) {otherdef(t);}

    此模板函数的参数t既可以接收左值,也可以接收右值。但仅仅使用右值引用作为函数模板的参数是远远不够的,还有一个问题需要解决,即如果调用function()函数时为其传递一个左值引用或右值引用的实参,如下所示:

    int n = 10;int &num = n;function(num); //T为int &int &&num2 = 11;function(num2); //T为int &&

    其中由function(num)实例化的函数底层就变成了function(int& &&t),而由function(num2)实例化的函数底层则变成了function(int&& &&t)。C++98是不支持这种语法的,而C++11为了更好地实现完美转发,专门为其指定了新的类型匹配规则,又称为引用折叠规则(假设A表示实际传递参数的类型):

    • 当实参为左值或左值引用(A&)时,函数模板中T&&将转变为A&(A& && = A&);

    • 当实参为右值或右值引用(A&&)时,函数模板中T&&将转变为A&&(A&& && = A&&);

    上述规则的含义是:在实现完美转发时,只要函数模板的参数类型为T&&,则C++可以自行准确地判定实际传入的实参是左值还是右值

    通过将函数模板的形参类型设置为T&&,我们可以很好地解决接收左、右值的问题。但除此之外,还需要解决一个问题,即无论传入的形参是左值还是右值,对于函数模板内部来说,形参既有名称又能寻址,因此它都是左值。那么如何才能将函数模板接收到的形参连同其左、右值属性,一起传递给被调用的函数呢?

    答案就是使用C++11提供的模板函数forward(),注意其和move的区别是forward要通过显式模板实参来使用

    用法示例:

    #include <iostream>#include <utility>using namespace std;//重载被调用函数,查看完美转发的效果template<typename T>void print(T &t){    cout << "lvalue" << endl;}template<typename T>void print(T &&t) {    cout << "rvalue" << endl;}template<typename T>void TestForward(T &&v) {    print(v);    print(std::forward<T>(v));    print(std::move(v));}int main() {    TestForward(1);  cout << endl;    int x = 1;    TestForward(x);    cout << endl;    TestForward(std::forward<int>(x));    return 0;}

    程序执行结果为:

    lvaluervaluervaluelvaluelvaluervaluelvaluervaluervalue

    到此,相信大家对“C++右值引用,移动语义与完美转发得方法”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

    --结束END--

    本文标题: C++右值引用,移动语义与完美转发得方法

    本文链接: https://lsjlt.com/news/325539.html(转载时请注明来源链接)

    有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

    猜你喜欢
    • C++右值引用,移动语义与完美转发得方法
      本篇内容主要讲解“C++右值引用,移动语义与完美转发得方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++右值引用,移动语义与完美转发得方法”吧!C++&mdash;&mda...
      99+
      2023-06-29
    • C++左值与右值,右值引用,移动语义与完美转发详解
      目录C++——左值与右值、右值引用、移动语义与完美转发一、左值和右值的定义二、如何判断一个表达式是左值还是右值(大多数场景)三、C++右值引用四、std::m...
      99+
      2024-04-02
    • C++中右值引用与移动语义的方法是什么
      今天小编给大家分享一下C++中右值引用与移动语义的方法是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。意义充分利用临时对...
      99+
      2023-07-05
    • C++精要分析右值引用与完美转发的应用
      目录区分左值与右值右值引用移动语义完美转发结语区分左值与右值 在C++面试的时候,有一个看起来似乎挺简单的问题,却总可以挖出坑来,就是问:“如何区分左值与右值?&rdqu...
      99+
      2024-04-02
    • C++11右值引用和移动语义的方法是什么
      本文小编为大家详细介绍“C++11右值引用和移动语义的方法是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“C++11右值引用和移动语义的方法是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。左值引用与右值...
      99+
      2023-07-05
    • C++11新特性之右值引用与完美转发详解
      目录一、左值与右值二、左值引用与右值引用三、右值引用应用1.移动构造与移动赋值1.模拟实现的string2.移动构造3.移动赋值四、默认移动构造和移动赋值重载函数五、完美转发1.万能...
      99+
      2024-04-02
    • 一文带你了解C++中的右值引用与移动语义
      目录意义左值右值值类别左值纯右值将亡值左值引用右值引用std::move()移动构造&移动赋值运算符重载测试&验证意义 充分利用临时对象,避免拷贝。 左值右值 值类别...
      99+
      2023-05-13
      C++右值引用 移动语义 C++右值引用 C++ 移动语义
    • C++11学习之右值引用和移动语义详解
      目录左值引用与右值引用1、左值与右值2、纯右值、将亡值3、左值引用与右值引用4、右值引用和 std::move 使用场景引用限定符const 和引用限定符移动语义—std...
      99+
      2023-02-23
      C++11右值引用 移动语义 C++11右值引用 C++11 移动语义
    • C++11右值引用和移动语义的实例解析
      目录基本概念左值 vs 右值左值引用 vs 右值引用右值引用使用场景和意义左值引用的使用场景左值引用的短板右值引用和移动语义右值引用引用左值右值引用的其他使用场景完美转发万能引用完美...
      99+
      2024-04-02
    • C++右值引用与移动构造函数应用的方法是什么
      这篇文章主要讲解了“C++右值引用与移动构造函数应用的方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++右值引用与移动构造函数应用的方法是什么”吧!1.右值引用右值引用是 C++...
      99+
      2023-07-05
    • C++11语法之右值引用的方法
      这篇文章主要讲解了“C++11语法之右值引用的方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++11语法之右值引用的方法”吧!一、{}的扩展在原先c++的基础上,C++11扩展了很多初...
      99+
      2023-06-29
    • C++右值引用与移动构造函数基础与应用详解
      目录1.右值引用1.1左值右值的纯右值将亡值右值1.2右值引用和左值引用2.移动构造函数2.1完美的移动转发1.右值引用 右值引用是 C++11 引入的与 Lambda 表达式齐名的...
      99+
      2023-02-13
      C++右值引用 C++移动构造函数
    • C++智能指针hared_ptr与右值引用的方法
      本篇内容主要讲解“C++智能指针hared_ptr与右值引用的方法”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C++智能指针hared_ptr与右值引用的方法”吧!目录 介绍 初始化方法1 通...
      99+
      2023-06-20
    • 深入学习C++智能指针之shared_ptr与右值引用的方法
      目录1. 介绍2. 初始化方法2.1 通过构造函数初始化2.2 通过拷贝和移动构造函数初始化2.3 通过 std::make_shared 初始化2.4 通过 reset 方法初始化...
      99+
      2024-04-02
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作