0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

聊聊std::move函数和std::forward函数

CPP开发者 来源:雨乐聊编程 2024-11-05 16:58 次阅读

以下文章来源于雨乐聊编程,作者雨乐

今天我们聊聊Modern cpp的两个非常重要的概念移动语义转发引用

概念

值类别

C++11之前,值类别分为左值和右值两种,但是自C++11起,引入了纯右值,消亡值两种。其中,左值和将亡值合称为泛左值,纯右值和将亡值合称为右值(C++11之前的右值等同于C++11中的纯右值)。因为本文目的不在于分析值类别,所以本文意义中的左值和右值就是字面意义上的左值右值。

右值(RVALUE),即立即创建和使用的临时值。在C++中,右值是与内存地址无关的表达式,这意味着其没有地址,也不能被修改。通常3、1.0以及std::string("abc")这种都属于右值。

PS:需要注意的是常量字符串"abc"等这种属于左值。

与右值相反,左值(LVALUE),其具有内存地址和可修改,其可以用于分配新值或者获取对象的地址。

可能有人有疑问,就是如何区分左值和右值,目前一个比较通用的判断方式就是:判断其是否可以取地址

左值引用 & 右值引用

既然有左值和右值,那么相应的,也就存在左值引用右值引用,常常如下这种表示:

inta=0;
int&la=a;
int&&r=3;

在上述示例中,a、la以及r都属于左值,其中la是左值引用,r是右值引用。

看下面一个例子:

#include

voidPrint(int&lref){
std::cout<< "Lvalue reference" << std::endl;
}

void Print(const int& lref) {
    std::cout << "const Lvalue reference" << std::endl;
}

void Print(int&& rref) {
    std::cout << "Rvalue reference" << std::endl;
}


int main() {
    int x = 5;
    const int y = 10;

    Print(x);   // lvalue reference
    Print(y);   // lvalue reference
    Print(20);  // rvalue reference

    return 0;
}

上述示例输出如下:

Lvaluereference
constLvaluereference
Rvaluereference

std::move

std::move是C++中的一个常用函数,它执行到右值引用的转换,允许您将左值转换为右值。这在您想要转移所有权或启用对象的移动语义的情况下非常有用。移动语义允许开发人员有效地将资源(如内存或文件句柄)从一个对象传输到另一个对象,而无需进行不必要的复制。

正如字面意义所理解的,移动语义允许将对象有效地从一个位置“移动”到另一个位置,而不是复制,这对于管理资源的对象特别有用。它实际上并没有移动任何东西;它只是将表达式的类型更改为右值引用。这允许调用移动构造函数或移动赋值运算符,而不是调用复制构造函数或复制赋值运算符。

gcc对move的实现如下:

template
inlinetypenamestd::remove_reference<_Tp>::type&&
move(_Tp&&__t)
{returnstatic_cast::type&&>(__t);}

也就是说,其仅仅通过static_cast<>做了类型转换~

std::move仅仅将对象转换为右值引用,仅此而已

#include
#include

classObj{
public:
Obj(){
std::cout<< "Default constructor
";
    }

    Obj(const Obj&) {
        std::cout << "Copy constructor
";
    }

    Obj(Obj&&) noexcept {
        std::cout << "Move constructor
";
    }

    Obj& operator=(Obj&& other) noexcept {
        std::cout << "Move assignment operator
";
        
        return *this;
    }

};

int main() {
    Obj obj1;                   /* Default constructor */
    Obj obj2 = std::move(obj1); /* Move constructor    */
    Obj obj3;
    obj3 = std::move(obj2);          /* Move assignment operator */
    return 0;
}

输出如下:

Defaultconstructor
Moveconstructor
Defaultconstructor
Moveassignmentoperator

在上述示例中:

•Obj1创建对象并调用构造函数•obj2是通过使用std::move移动obj1创建的,它调用移动构造函数•创建obj3并调用默认构造函数•当使用std::move将obj2移动到 obj3 时,将调用移动赋值运算符

在此示例中,使用std::move操作, obj1到obj2 以及 obj2到obj3调用的是移动的行为,这样可以提高性能,尤其是在移动大型数据结构或资源时。但是,重要的是要注意移动对象的状态及其拥有的资源。

#include
#include

classObj{
public:
Obj(){
std::cout<< "Obj constructed" << std::endl;
    }

    ~Obj() {
        std::cout << "Obj destructed" << std::endl;
    }

    void fun() {
        std::cout << "in fun" << std::endl;
    }
};

int main() {
    std::unique_ptrp1=std::make_unique();

std::unique_ptrp2=std::move(p1);
if(p1){
std::cout<< "p1 is not empty" << std::endl;
    }

    p2->fun();

return0;
}

在这个例子中,首先创建了一个类型为std::unique_ptr的指针p1,然后通过调用std::move()将p1的所有权转移至p2,接着判断p1是否为有效的指针,如果是则输出,接着p2调用fun()函数。

上述示例输出结果如下:

Objconstructed
infun
Objdestructed

从这个输出结果可以看出,通过std::move()将所有权从p1转移至p2后,p1不再持有任何资源。

std::forward

std::forward是 C++ 标准库中的一个函数模板,用于在模板函数中进行完美转发。它允许在模板函数中将参数转发到另一个函数,同时保持参数的值类别(value category)和 cv 限定符(const 或 volatile 限定符)不变。

std::forward通常与右值引用(&&)结合使用,用于转发传递给模板函数的参数。在模板函数内部,你可以使用std::forward来将参数转发给其他函数,并保持原始参数的性质。

示例如下:

#include

voidPrint(constint&lref){
std::cout<< "Lvalue reference" << std::endl;
}

void Print(int&& rref) {
    std::cout << "Rvalue reference" << std::endl;
}

template
voidFun(T&&param){
Print(std::forward(param));
}

intmain(){
intx=5;
constinty=10;

Fun(x);//lvaluereference
Fun(y);//lvaluereference
Fun(20);//rvaluereference

return0;
}

在这个例子中,我们创建了一个模板函数Fun(),其参数类型为T&&,当使用左值调用Fun()时候,它将param作为左值进行转发,当使用右值调用Fun()时候,它将param作为右值进行转发,然后调用对应的函数,这样可保证在不损失真实类型的情况下调用正确的函数。

move vs forward

对于第一次接触这块知识点的开发人员来说,可能有点疑惑,是否可以用move来替代forward,我们且看一个例子,相信你看了之后就不会对这块一目了然:

#include


voidPrint(int&a){
std::cout<< "int&: " << a << std::endl;
}


void Print(int&& a) {
    std::cout << "int&&: " << a << std::endl;
}

template 
voidfunc1(T&&a){
Print(std::move(a));
}


template
voidfunc2(T&&a){
Print(std::forward(a));
}

intmain(){
intarg=10;

std::cout<< "Calling func1 with std::move()..." << std::endl;
    func1(arg); /* arg is an lvalue */
    func1(25);  /* 25 is an rvalue  */

    std::cout << "Calling func2 with std::forward()..." << std::endl;
    func2(arg); /* arg is an lvalue */
    func2(25);  /* 25 is an rvalue  */

    return 0;
}

上述代码输出如下:

Callingfunc1withstd::move()...
int&&:10
int&&:25
Callingfunc2withstd::forward()...
int&:10
int&&:25

在上述代码中:

•创建了两个重载函数Print,其参数类型分别为**int &和int &&**,函数的功能是输出其参数的类型

•模板函数func1(),函数参数a为转发引用(T&&,也有地方称之为万能引用),函数体内调用参数为std::move(a)的Print()函数,将a转换为右值引用,这意味着,如果a是左值,则传递给Print()函数的参数类型为右值引用

•模板函数func2(),与模板函数func1()一样,该函数也采用转发引用(T&&)。但是,它使用 std::forward来保留a的原始值类别。这意味着如果a是左值,它将作为左值传递给Print()函数,如果它是右值,它将作为右值传递

•在 main() 中,使用左值和右值调用函数func1和func2,以观察对应的行为

通过上面输出,基本可以区分这俩,在此,做下简单的总结:

•目的

•std::forward:用于完全按照传递的参数转发,保留其值类别(左值或右值)

•std::move:用于将对象转换为右值引用,通常用于启用移动语义并转移所有权

•用法

•std::forward:通常用于转发引用(通用引用),以保留传递给另一个函数的参数的值类别

•std::move:用于将对象显式转换为右值引用

•影响

•std::forward:不更改参数的值类别。如果原始参数是右值引用,则它返回右值引用,否则返回左值引用

•std::move:将其参数转换为右值引用,将其值类别更改为右值

•安全

•std::forward:可以安全地与转发引用 (T&&) 一起使用,以确保正确转发参数,而不会产生不必要的副本。

•std::move:应谨慎使用,因为它可能会导致从其他地方仍需要的对象移动,从而导致未定义的行为

•场景

•std::forward:用于需要完美转发参数的场景,例如模板函数和类中。

•std::move:在显式转移所有权或调用移动语义时使用,例如从函数返回仅移动类型时

•返回类型

•std::forward:返回类型取决于传递给它的参数的值类别,它可以返回左值引用或右值引用。

•std::move:始终返回右值引用

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内存
    +关注

    关注

    8

    文章

    3019

    浏览量

    74008
  • 字符串
    +关注

    关注

    1

    文章

    578

    浏览量

    20508
  • 函数
    +关注

    关注

    3

    文章

    4327

    浏览量

    62573
  • C++
    C++
    +关注

    关注

    22

    文章

    2108

    浏览量

    73623

原文标题:性能大杀器:std::move 和 std::forward

文章出处:【微信号:CPP开发者,微信公众号:CPP开发者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    SAC-STD标准

    本帖最后由 eehome 于 2013-1-5 09:48 编辑 那位工程师有关于SAC-STD-001 Certification StandardSAC-STD-002 Audit
    发表于 11-17 18:57

    std::inner_product函数运算特别缓慢如何优化?

    ();for(,,,)std::inner_product(samples);i2s_write(samples);结果就是,inner_product占用了大量的时间,导致音频没有办法及时的处理,在esp32 Lyra-T
    发表于 02-15 08:11

    如何使用std::future/std::promise 和 std::packaged_task来促进esp32上的多线程?

    我正在考虑使用 std::future/std::promise 和 std::packaged_task 来促进 esp32 上的多线程。我知道多任务函数之间的抽象分层如下:
    发表于 03-02 08:44

    IEEE Std C37.09-1999标准

    IEEE Std C37.09™-1999(Revision ofIEEE Std C37.09-1979) Errata toIEEE Standard Test Procedure
    发表于 07-24 22:18 12次下载

    IEEE Std 802.1ad-2005标准

    IEEE Std 802.1ad™-2005(Amendment toIEEE Std 802.1QTM-2005)IEEE Standard forLocal
    发表于 07-27 18:20 62次下载

    IEEE Std C57.12.37™-2006

    IEEE Std C57.12.37™-2006(Revision ofIEEE Std 1388™-2000)IEEE Standard
    发表于 07-27 18:28 16次下载

    IEEE STD C57.12.59-2001标准

    IEEE STD C57.12.59-2001标准,IEEE STD C57.12.59-2001标准
    发表于 07-27 18:32 27次下载

    IEEE Std C57.12.90™-2006

    IEEE Std C57.12.90TM-2006(Revision ofIEEE Std C57.12.90-1999)IEEE Standard Test Codefor
    发表于 07-27 18:33 54次下载

    IEEE Std 1226-1998

    IEEE Std 1226-1998(Revision ofIEEE Std 1226-1993)IEEE ABBET™IEEE Trial-Use Standard for A
    发表于 08-11 23:46 26次下载

    IEEE Std C37.081a-1997

    IEEE Std C37.081a-1997(Supplement to IEEE Std C37.081-1981)Supplement to IEEE Guide
    发表于 08-11 23:50 21次下载

    一篇“非典型”面试经验贴在Reddit上引发热议

    你的代码中:包括模板、继承、指针,引用,std :: vector,std :: unordered_map,std :: movestd
    的头像 发表于 04-30 08:48 2703次阅读

    std::function简介及模板类声明

    01 — std::function简介 std::function是一个函数包装器,该函数包装器模板能包装任何类型的可调用实体,如普通函数
    的头像 发表于 07-28 15:30 4811次阅读

    在C++中如何用虚函数实现多态

    。 // virtual_function.cpp : 此文件包含 “main” 函数。程序执行将在此处开始并结束。 // #include 《iostream》 class Base { public: Base() { std
    的头像 发表于 09-29 14:18 1694次阅读

    C++ std::tie函数的作用和用法

    C++中std::tie函数的作用就是从元素引用中生成一个tuple元组,其在头文件中定义
    的头像 发表于 07-18 17:28 844次阅读

    动态数组和C++ std::vector详解

    std::vector是C++的默认动态数组,其与array最大的区别在于vector的数组是动态的,即其大小可以在运行时更改。std::vector是封装动态数组的顺序容器,且该容器中元素的存取是连续的。
    的头像 发表于 07-19 11:07 966次阅读