首页 > 编程语言 >c++右值引用、移动语义、完美转发

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

时间:2023-11-07 19:33:18浏览次数:40  
标签:std 右值 int 语义 左值 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

看如下例子,

#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。

 

 

 

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

https://www.jb51.net/article/278300.htm

标签:std,右值,int,语义,左值,c++,引用,&&
From: https://www.cnblogs.com/spacerunnerZ/p/17815655.html

相关文章

  • Windows10+VSCode+CMake+shell脚本编译C/C++程序
    一、概述想要在Windows10上做C++验证/编译类库,借助VSCode(其实这东西要不要都行,它就是来方便查看代码的)+CMake+shell脚本做程序的编译运行。下面写一个小例子记录一下准备工作:1.编译环境用的是mingw64,使用其再带的g++编译,ps:记得要配置其环境变量2......
  • 【01】安装与配置 C++/Visual Studio 22 | PDCurses on Windows
    参考:https://www.cnblogs.com/yapingxin/p/15936414.html实践、概括、优化:编译生成下载源码,解压后进入其中的wincon目录;如果需要为多个Platform(x86和x64)以及多个分支(Debug和Release),多复制备份几个wincon文件夹,分别命名好;编辑其中的Makefile.vc文件,在11行下新建一行,写入:PL......
  • 11 个最佳 C++ IDE(和代码编辑器)
    C++是一种功能强大、用途广泛的编程语言。它也可以是一个艰难的大师。这意味着在您的工具带中拥有正确的工具以帮助您更高效、更有效、更自信地编写代码至关重要。在为C++编程寻找最佳IDE或代码编辑器时,您应该从哪里开始?IDE选项列表几乎是无限的,很难判断哪个是最适合您的软......
  • C++中的高阶函数 -- std::function实现回调
    C++中的高阶函数:以std::function优雅地实现回调1.简介1.1C++高阶函数的概念在函数式编程语言中,高阶函数(Higher-orderFunction)是一个常见的概念,它通常被定义为满足下列条件之一的函数: 接受一个或多个函数作为输入(参数)输出(返回值)是一个函数C++作为一门多范式编程语言,也......
  • C++ lambda函数总结
    C++lambda函数1lambda函数简介名称lambda来自lambdacalculus(lambda演算),一种定义和应用函数的数学系统。这个系统中可以使用匿名函数,对于接收函数指针或伪函数的函数,可以使用匿名函数定义(lambda)作为其参数。1.1为什么使用lambda函数?距离:定义位于使用的地方附近很有用,由于......
  • Microsoft Visual C++ 14.0 is required.
    问题:配置detectron2的时候报错,MicrosoftVisualC++14.0isrequired.解决:按照上面的网址去下载MicrosoftC++BulidTools这个工具,安装对应的包即可 ......
  • C++禁用windows全局鼠标
    禁用全局鼠标的实现方式与禁用键盘类似,也是通过使用WindowsAPI函数来创建钩子来截取鼠标消息,然后在钩子函数中阻止特定鼠标事件的执行。下面是一个使用C++和WindowsAPI来禁用全局鼠标的示例代码:#include<iostream>#include<Windows.h>//定义全局的钩子句柄HHOOKmouseHook......
  • C++禁用windows全局键盘
    1.使用WindowsAPI函数调用来拦截键盘消息。2.创建一个键盘钩子来截取键盘消息。3.在钩子函数中,检测到特定按键事件时,阻止该事件执行。4.最终在程序退出时释放钩子。下面是一个使用C++和WindowsAPI来禁用Windows系统键盘的示例代码:#include<iostream>#include<Windows.h......
  • c/c++数据类型
    intunsignedintcharunsignedcharlonglonglongunsignedlongshortunsignedshortfloatdouble各种指针类型枚举类型struct结构类型union联合类型boolstring类 比如把数据类型比做盒子,定义类型就是存仓什么东西,假如int型把盒子分成4份......
  • C++模板显示指定类型时使用引用遇到的问题
    1.问题这里我想让模板函数接收int和char类型的参数,并进行相加,显示指定参数类型为int。第一个调用理论上会自动将char类型强转成int类型,后进行相加;第二个调用理论上会自动将int类型强转成char类型,后进行相加;但是报错Nomatchingfunctionforcallto'add_ab'template<typena......