c++右值引用、移动语义、完美转发

这篇具有很好参考价值的文章主要介绍了c++右值引用、移动语义、完美转发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 左值、右值、左值引用以及右值引用

  • 左值:一般指的是在内存中有对应的存储单元的值,最常见的就是程序中创建的变量
  • 右值:和左值相反,一般指的是没有对应存储单元的值(寄存器中的立即数,中间结果等),例如一个常量,或者表达式计算的临时变量
int x = 10 
int y = 20
int z = x + y 
//x, y , z 是左值
//10 , 20,x + y 是右值,因为它们在完成赋值操作后即消失,没有占用任何资源
  • 左值引用:C++中采用 &对变量进行引用,这种常规的引用就是左值引用
  • 右值引用:右值引用最大的作用就是让一个左值达到类似右值的效果(下面程序举例),让变量之间的转移更符合“语义上的转移”,以减少转移之间多次拷贝的开销。右值引用符号是&&。

例如,对于以下程序,我们要将字符串放到vector中,且我们后续的代码中不再用到x:

std::vector<std::string> vec;
std::string x = "abcd";
vec.push_back(x);
std::cout<<"x: "<<x<<"\n";
std::cout<<"vector: "<< vec[0]<<"\n";

//-------------output------------------
// x: abcd
// vector: abcd

 该程序在真正执行的过程中,实际上是复制了一份字符串x,将其放在vector中,这其中多了一个拷贝的开销和内存上的开销。但如果x以及没有作用了,我们希望做到的是 真正的转移,即x指向的字符串移动到vector中,不需要额外的内存开销和拷贝开销。因此我们希望让变量 x传入到push_back 表现的像一个右值 ,这个时候就体现右值引用的作用,只需要将x的右值引用传入就可以。

改进成如下代码:

std::vector<std::string> vec;
std::string x = "abcd";
vec.push_back(std::move(x));              <---------------  使用了std::move,任何的左值/右值通过std::move都转化为右值引用
std::cout<<"x: "<<x<<"\n";
std::cout<<"vector: "<< vec[0]<<"\n";
//-------------output------------------
// x: 
// vector: abcd

可以看到,完成`push_back`后x是空的。

 

 2. 移动语义

 移动语义是通过移动构造移动赋值避免无意义的拷贝操作。

2.1 使用std::move实现移动构造

定义:采用右值引用作为参数的构造函数又称作移动构造函数。此时不需要额外的拷贝操作,也不需要新分配内存。

使用场景:对于一个值(比如数组、字符串、对象等)如果在执行某个操作后不再使用,那么这个值就叫做将亡值(Expiring Value),因此对于本次操作我们就没必要对该值进行额外的拷贝操作,而是希望直接转移,尽可能减少额外的拷贝开销,操作后该值也不再占用额外的资源。

使用函数:std::move,任何的左值/右值通过std::move都转化为右值引用

看如下例子,

#include <iostream>
#include <vector>
#include <string>

class A {
  public:
    A(){}
    A(size_t size): size(size), array((int*) malloc(size)) {
        std::cout 
          << "create Array,memory at: "  
          << array << std::endl;
        
    }
    ~A() {
        free(array);
    }
    A(A &&a) : array(a.array), size(a.size) {
        a.array = nullptr;
        std::cout 
          << "Array moved, memory at: " 
          << array 
          << std::endl;
    }
    A(A &a) : size(a.size) {
        array = (int*) malloc(a.size);
        for(int i = 0;i < a.size;i++)
            array[i] = a.array[i];
        std::cout 
          << "Array copied, memory at: " 
          << array << std::endl;
    }
    size_t size;
    int *array;
};
int main() {
    std::vector<A> vec;
    A a = A(10);
    vec.push_back(a);   
    return 0;   
}

//----------------output--------------------
// create Array,memory at: 0x600002a28030 // A a = A(10); 调用了 构造函数A(size_t size){}
// Array copied, memory at: 0x600002a28050 //vec push的时候拷贝一份,调用构造函数A(A &a){}

从输出可以看到,每次进行push_back的时候,会重新创建一个对象,调用了左值引用A(A &a) : size(a.size)对应的构造函数,将对象中的数组重新深拷贝一份。

如果该用右值引用进行优化,如下

int main () {
    std::vector<A> vec;
    A a = A(10);
    vec.push_back(std::move(a));   
    return 0;   
}

//----------------output--------------------
// create Array,memory at: 0x600003a84030
// Array moved, memory at: 0x600003a84030

可以看到,这个时候虽然也重新创建了一个对象,但是调用的是这个构造函数A(A &&a) : array(a.array), size(a.size)(这种采用右值引用作为参数的构造函数又称作移动构造函数),此时不需要额外的拷贝操作,也不需要新分配内存。

 

3. 完美转发

使用函数:std::forward,如果传递的是左值转发的就是左值引用,传递的是右值转发的就是右值引用。

 

3.1 引用折叠

在具体介绍std::forward之前,需要先了解C++的引用折叠规则,对于一个值引用的引用最终都会被折叠成左值引用或者右值引用。

  • T& & -> T& (对左值引用的左值引用是左值引用)
  • T& && -> T& (对左值引用的右值引用是左值引用)
  • T&& & ->T& (对右值引用的左值引用是左值引用)
  • T&& && ->T&& (对右值引用的右值引用是右值引用)

总结一句话,只有对于右值引用的右值引用折叠完还是右值引用,其他都会被折叠成左值引用。

 

3.2 使用std::forward实现完美转发

std::forward的作用就是完美转发,确保转发过程中引用的类型不发生任何改变,左值引用转发后一定还是左值引用,右值引用转发后一定还是右值引用!

下面是一个使用 std::forward 的例子:

#include <iostream>
#include <utility>
 
void func(int& x) {
    std::cout << "lvalue reference: " << x << std::endl;
}
 
void func(int&& x) {
    std::cout << "rvalue reference: " << x << std::endl;
}
 
template<typename T>
void wrapper(T&& arg) {
    func(std::forward<T>(arg));
}
 
int main() {
    int x = 42;
    wrapper(x);  // lvalue reference: 42
    wrapper(1);  // rvalue reference: 1
    return 0;
}

在上面的例子中,我们定义了两个函数 func,一个接受左值引用,另一个接受右值引用。然后我们定义了一个模板函数 wrapper,在 wrapper 函数中,我们使用 std::forward 函数将参数 arg 转发给 func 函数。通过使用 std::forward,我们可以确保 func 函数接收到的参数的左右值特性与原始参数保持一致。

  • 当向wrapper里面传入x的时候,wrapper推导认为 T是一个左值引用int &,通过引用折叠原则(看万能引用文章)int && + & = int &,相当于wrapper(int& arg),同时我们知道了T推导为int&,那么在向func传递的时候,就是func(std::forward<int&> (arg)) ,那么func会以左值引用的形式 func(int& x) 调用arg。
  • 当向wrapper里面传入1的时候,wrapper推导认为T是一个右值引用int&& ,通过引用折叠原则,int && + && =int&& ,相当于wrapper(int&& arg),同时我们知道了T推导为int&&,那么在向func传递的时候,就是func(std::forward<int&&>(arg)),那么func会以左值引用的形式func(int&& x)调用arg。

 

另一个例子:

class Test{};

void B(Test& a) {cout << "B&"  << endl;}
void B(Test&& a) {cout << "B&&" << endl;}
template<typename T> void A(T &&a) { B(std::forward<T>(a)); }

int main()
{
  Test a;
  A(std::move(a));
  A(a);
  return 0;    
}


//////
//输出结果
B&&
B&

 

 

 

参考链接:https://zhuanlan.zhihu.com/p/469607144

https://www.jb51.net/article/278300.htm文章来源地址https://www.toymoban.com/news/detail-745905.html

到了这里,关于c++右值引用、移动语义、完美转发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • C++左值右值完美转发转移

    英文含义: 左值(Lvalue) : Locator value ,意味着它指向一个具体的内存位置。 右值(Rvalue) : Read value ,指的是可以读取的数据,但不一定指向一个固定的内存位置。 定义 左值 :指的是一个持久的内存地址。左值可以出现在赋值操作的左侧或右侧。例如,变量、数组的元

    2024年03月10日
    浏览(44)
  • 【C++11】 initializer_list | 右值引用 | 移动构造 | 完美转发

    { } 初始化 C++11 扩大了括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义类型, 使用初始化列表,可添加等号(=),也可不添加 将1赋值给x1,x2处省略了赋值符号,将5赋值给x2 同样也可以将new开辟4个int的空间初始化为0 创建对象时,可以使用列

    2024年02月08日
    浏览(58)
  • c++积累8-右值引用、移动语义

    1.1 背景 c++98中的引用很常见,就是给变量取个别名,具体可以参考c++积累7 在c++11中,增加了右值引用的概念,所以c++98中的引用都称为左值引用 1.2 定义 右值引用就是给右值取个名字,右值有了名字之后就成了普通变量,可以像使用左值一样使用。 语法:数据类型 变量名

    2023年04月23日
    浏览(29)
  • 【C++学习】C++11——新特性 | 右值引用 | 完美转发

    🐱作者:一只大喵咪1201 🐱专栏:《C++学习》 🔥格言: 你只管努力,剩下的交给时间! C++的发展截至到目前为止,虽然版本有很多,但是C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一

    2024年02月06日
    浏览(26)
  • 【C++】—— C++11新特性之 “右值引用和移动语义”

    前言: 本期,我们将要的介绍有关 C++右值引用 的相关知识。对于本期知识内容,大家是必须要能够掌握的,在面试中是属于重点考察对象。 目录 (一)左值引用和右值引用 1、什么是左值?什么是左值引用? 2、什么是右值?什么是右值引用? (二)左值引用与右值引用比

    2024年02月11日
    浏览(28)
  • 详解 C++ 左值、右值、左值引用以及右值引用

    左值是一个表示数据的表达式,比如: 变量名、解引用的指针变量 。一般地,我们可以 获取它的地址 和 对它赋值 ,但被 const 修饰后的左值,不能给它赋值,但是仍然可以取它的地址。 总体而言,可以取地址的对象就是左值。 右值也是一个表示数据的表达式,比如: 字面

    2023年04月08日
    浏览(37)
  • 从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值

    目录 1. 列表初始化initializer_list 2. 前面提到的一些知识点 2.1 小语法 2.2 STL中的一些变化 3. 右值和右值引用 3.1 右值和右值引用概念 3.2 右值引用类型的左值属性 3.3 左值引用与右值引用比较 3.4 右值引用的使用场景 3.4.1 左值引用的功能和短板 3.4.2 移动构造 3.4.3 移动赋值

    2024年02月12日
    浏览(26)
  • [开发语言][c++]:左值、右值、左值引用、右值引用和std::move()

    写在前面: 如果你也被 左值、右值、左值引用、右值引用和std::move 搞得焦头烂额,相关概念和理解不够深入,或者认识模棱两可,那么这篇文章将非常的适合你,耐心阅读,相信一定会有所收获~~ 左值: 可以取地址、位于等号左边 – 表达式结束后依然存在的持久对象

    2024年02月02日
    浏览(51)
  • 【C++】 C++11(右值引用,移动语义,bind,包装器,lambda,线程库)

    C++11是C++语言的一次重大更新版本,于2011年发布,它包含了一些非常有用的新特性,为开发人员提供了更好的编程工具和更好的编程体验,使得编写高效、可维护、安全的代码更加容易。 一些C++11的新增特性包括: 强制类型枚举,使得枚举类型的通常行为更加可靠和容易控制

    2024年02月10日
    浏览(34)
  • 初识C++之左值引用与右值引用

    目录 一、左值引用与右值引用 1. 左值和右值的概念 1.1 左值 1.2 右值  1.3 左值与右值的区分 2. 左值引用与右值引用 2.1 左值引用与右值引用的使用方法 2.2 左值引用的可引用范围 2.3 右值引用的可引用范围 3. 右值引用的作用 3.1 减少传值返回的拷贝 3.2 插入时的右值引用 4

    2023年04月26日
    浏览(28)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包