C++右值引用(左值表达式、右值表达式)(移动语义、完美转发(右值引用+std::forward))(有问题悬而未决)

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

什么是左值和右值?(右值不能被取地址)

在 C++ 中,表达式可以分为左值表达式和右值表达式。左值表达式指的是可以出现在赋值语句左边的表达式,例如变量、数组元素、结构体成员等;右值表达式指的是不能出现在赋值语句左边的表达式,例如常量、临时对象、函数返回值等。

右值是指将要被销毁的临时对象或者没有名字的临时对象。例如,一个返回临时对象的函数调用表达式、一个匿名对象、一个类型转换表达式等都是右值表达式,它们都是将要被销毁的临时对象或者没有名字的临时对象。

右值的特点是它们没有持久的身份,不能被取地址,不能被修改,只能被使用一次。因此,右值引用的主要作用是支持移动语义和完美转发,从而提高程序的效率。(注意:字符串常量也是左值,因为它们被存储在静态数据区,虽然不能被更改,但是能够被取地址)

在 C++11 中,引入了右值引用和移动语义的概念,使得程序可以更好地利用右值,提高程序的效率。

下面是一些右值的例子:

int a = 1;  // a 是左值,1 是右值
int b = a + 2;  // b 是左值,a + 2 是右值
int* p = &a;  // p 是左值,&a 是右值
int c = func();  // c 是左值,func() 是右值
int d = std::move(a);  // d 是左值,std::move(a) 是右值

在上面的例子中,1、2、&a、func()、std::move(a) 都是右值,它们都是将要被销毁的临时对象或者没有名字的临时对象。左值 a、b、p、c、d 都是可以被取地址、可以被修改、有持久的身份的对象,它们都是左值。

需要注意的是,一个对象既可以是左值,也可以是右值,这取决于它在表达式中的位置。例如,在赋值语句左边的对象是左值,在赋值语句右边的对象是右值。

右值分为纯右值和将亡值

prvalue(pure rvalue, 纯右值)

右值是临时产生的值,不能对右值取地址,因为它本身就没存在内存地址空间上。

举例纯右值如下:

  • 除字符串以外的常量,如 1,true,nullptr
  • 返回非引用的函数或操作符重载的调用语句。
  • a++, a–是右值
  • a+b, a << b 等
  • &a,对变量取地址的表达式是右值。
  • this指针
  • lambda表达式

理解也很简单,其实就是一些运算时的中间值,这些值只存在寄存器中辅助运算,不会实际写到内存地址空间中,因此也无法对他们取地址。

xvalue(eXpiring value, 将亡值)

  • 返回右值引用的函数或者操作符重载的调用表达式。如某个函数返回值是 std::move(x),并且函数返回类型是 T&&
  • 目标为右值引用的类型转换表达式。如 static<int&&>(a)

参考文章:https://www.zhihu.com/question/363686723/answer/2590214399

示例:

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // std::move(v1)是一个将亡值

在上面的代码中,std::move(v1)返回的是一个将亡值,因为v1的资源所有权即将被转移给v2,v1即将被销毁。

什么是左值引用(非const左值引用 和 const左值引用)

左值引用可以分为两种:非const左值引用 和 const左值引用。

非const左值引用只能绑定左值;const左值引用既能绑定左值,又能绑定右值。

#include <iostream>
#include <cstdarg>

using namespace std;

int main()
{
    int a = 1;
    int &lref_a = a;
    lref_a++; // 通过非 const 左值引用可以修改其值

    const int &lref_const_a = a;
    // lref_const_a++; // error, const左值引用不能修改其值

    const int &lref_const_rvalue = 999; // const 左值引用可以直接绑定右值 999
    // int &lref_const_rvalue = 999;                            // 错误,非常量引用的初始值必须为左值(为什么要这么设计暂时没太搞明白,说是存在于栈上,那为啥不让更改呢?可能是出于什么安全考虑?)
    cout << "lref_const_rvalue = " << lref_const_rvalue << endl;   // lref_const_rvalue = 999
    cout << "&lref_const_rvalue = " << &lref_const_rvalue << endl; //&lref_const_rvalue = 0x7ffd28c02a0c

    return 0;
}

可以看到,lref_const_rvalue 是 const 左值引用,但是他直接绑定到一个右值(数字常量999)上了。

有没有想过为什么c++要这么设计呢?

举个例子,你要设计 print 方法。如何设计 print 方法的参数呢?

首先,考虑到值传递参数会产生额外的拷贝,这是难以接受的。于是你想到了引用传递(你要用指针?那这篇文章不用看了。。。)

void print(int& a);

于是添加数据需要这样:

int a = 1;
print(a);

好像有点麻烦,有时候你只需要添加一个常量(数字常量就是右值)进去,你还得首先声明一个变量,有点麻烦,如果能直接这样添加就好了:

print(1);

也就是说,无论入参是左值和右值,print函数都能正常接收。于是,用const左值引用可以解决这个问题。实际上不知不觉中我们很多代码都用到了这种参数形式。

void print(const int& a);

然后就只可以直接 print(1) 了。当然,由于是 const 左值引用,因此你无法修改其值。只可读不可写。

ok,左值引用掌握到这种程度就可以了。接下来是右值引用。

参考文章:https://www.zhihu.com/question/363686723/answer/2590214399

什么是右值引用?

右值引用是 C++11 引入的新特性,用于支持移动语义和完美转发。右值引用的语法是在类型名后面加上两个引用符号 &&,例如 int&& 表示对一个右值 int 对象的引用。

右值引用的主要作用是支持移动语义,即将一个对象的资源(内存、指针等)移动到另一个对象中,从而避免了不必要的内存拷贝和资源分配。移动语义可以提高程序的效率,特别是当对象较大时,避免了不必要的内存拷贝和资源分配,从而提高了程序的性能。

右值引用还可以用于完美转发,即将一个函数的参数以原样转发给另一个函数,从而避免了不必要的拷贝和转换。完美转发可以提高程序的效率,特别是当函数参数较大或者类型较复杂时,避免了不必要的拷贝和转换,从而提高了程序的性能。

需要注意的是,右值引用只能绑定到一个右值对象,不能绑定到一个左值对象。如果尝试将一个左值对象绑定到一个右值引用上,编译器会报错。

下面是一个右值引用的例子:

#include <iostream>
#include <string>
using namespace std;

void print(std::string &&str)
{
    cout << str << endl;
    cout << "&str=" << &str << endl; //&str=0x7ffe64500230
}

int main()
{
    std::string s = "Hello, world!";
    cout << "&s=" << &s << endl; //&s=0x7ffe64500230
    print(std::move(s));         // 将左值 s 转换为右值引用
    return 0;
}

在上面的例子中,print 函数的参数是一个右值引用 std::string&&,表示对一个右值 std::string 对象的引用。在 main 函数中,我们定义了一个左值 std::string 对象 s,然后将它转换为右值引用 std::move(s),并将它作为参数传递给 print 函数。

由于 std::move(s) 返回的是一个右值引用,因此可以绑定到 print 函数的参数上。在 print 函数中,我们可以使用 str 来访问传递进来的字符串,而不需要进行不必要的拷贝和转换。

需要注意的是,由于 std::move 只是将一个左值对象转换为右值引用,它并不会移动对象的资源。如果需要移动对象的资源,需要在移动构造函数或移动赋值运算符中使用右值引用。

注意:上面str参数虽然声明为右值,但是既可以当右值用,也可以当左值用:

#include <iostream>
#include <string>
using namespace std;

void print(std::string &&str)
{
    str = "Hello, baby!";
    cout << "str=" << str << endl; // str=Hello, baby!
}

int main()
{
    std::string s = "Hello, world!";
    print(std::move(s));       // 将左值 s 转换为右值引用
    cout << "s=" << s << endl; // s=Hello, baby!
    return 0;
}

什么是移动语义?(移动构造函数的实现,将一个对象的资源转移到另一个对象)

移动语义是 C++11 引入的一个新特性,它可以将对象的资源(比如内存、文件句柄等)从一个对象转移到另一个对象,避免了不必要的复制和销毁操作,提高了程序的性能。

在移动语义中,右值引用扮演了重要的角色。只有右值引用才能绑定到临时对象或将要销毁的对象,从而实现资源的转移。如果使用左值引用代替右值引用,就无法实现移动语义的效果。

下面是一个使用右值引用实现移动语义的例子:

#include <iostream>
#include <string>
#include <cstring>
using namespace std;

class MyString
{
public:
    MyString() : m_data(nullptr), m_size(0) {}
    MyString(const char *str) : m_data(new char[strlen(str) + 1]), m_size(strlen(str))
    {
        strcpy(m_data, str);
    }
    MyString(MyString &&other) : m_data(other.m_data), m_size(other.m_size)
    {
        other.m_data = nullptr;
        other.m_size = 0;
    }
    ~MyString()
    {
        delete[] m_data;
    }
    void print() const // 常量成员函数
    {
        if (!m_data)
        {
            std::cout << "m_data is null, return!" << std::endl;
            return;
        }
        std::cout << m_data << std::endl;
    }

private:
    char *m_data;
    size_t m_size;
};

int main()
{
    MyString s1("Hello, world!");
    s1.print(); // 输出 "Hello, world!"

    cout << "------------------" << endl;

    MyString s2(std::move(s1)); // 将 s1 转移为右值引用
    s1.print();                 // 输出空字符串

    cout << "------------------" << endl;

    s2.print(); // 输出 "Hello, world!"

    cout << "------------------" << endl;

    return 0;
}

在上面的代码中,我们定义了一个 MyString 类,它包含一个字符数组和一个大小成员变量。在类的构造函数中,我们使用 new 运算符为字符数组分配内存,并将字符串复制到数组中。在移动构造函数中,我们将其他对象的指针和大小成员变量移动到当前对象中,并将其他对象的指针和大小成员变量设置为 null 和 0。这样,我们就实现了将一个 MyString 对象的资源转移到另一个对象的功能。在 main 函数中,我们创建了两个 MyString 对象 s1 和 s2,然后将 s1 转移为右值引用,从而实现了移动语义的效果。

上面代码还有点bug,我已经上知乎问了。。。是因为我把s1的m_data销毁了,还打印它,结果程序就莫名崩溃了。。后来我修复了

代码运行结果:

C++右值引用(左值表达式、右值表达式)(移动语义、完美转发(右值引用+std::forward))(有问题悬而未决)

什么是完美转发?(右值引用+std::forward)

C++右值引用完美转发是一种技术,用于在函数调用中将参数以原样传递给另一个函数,同时保持参数的值类别(左值或右值)。这种技术可以提高代码的效率和可读性。

在C++11中,引入了右值引用和std::forward函数,使得完美转发成为可能。右值引用是一种新的引用类型,可以绑定到右值(临时对象或表达式的结果)也可以绑定到const左值,而左值引用只能绑定到左值(具有持久性的对象)。std::forward函数是一个模板函数,用于将参数以原样转发给另一个函数。

使用右值引用完美转发可以避免不必要的对象拷贝和移动,提高代码的效率。同时,它还可以保持参数的值类别,避免了一些潜在的问题,例如在函数模板中传递参数时,如果不使用完美转发,可能会导致参数的值类别发生改变,从而影响函数的行为。

下面是一个使用右值引用完美转发的示例:

#include <iostream>
#include <string>
#include <cstring>
using namespace std;

void bar(int &x)
{
    std::cout << "lvalue: " << x << std::endl;
}

void bar(int &&x)
{
    std::cout << "rvalue: " << x << std::endl;
}

template <typename T, typename... Args>
void foo(Args &&...args)
{
    bar(std::forward<Args>(args)...);
}

int main()
{
    int x = 1;
    foo<int>(x); // lvalue: 1
    foo<int>(2); // rvalue: 2
    return 0;
}

在这个示例中,函数foo使用了右值引用完美转发,将参数args以原样传递给函数bar。bar有两个重载版本,一个接受左值引用,一个接受右值引用,因此可以根据参数的值类别选择正确的版本。在main函数中,分别调用了foo<int>(x)foo<int>(2),分别传递了一个左值和一个右值,输出了对应的结果。文章来源地址https://www.toymoban.com/news/detail-439973.html

到了这里,关于C++右值引用(左值表达式、右值表达式)(移动语义、完美转发(右值引用+std::forward))(有问题悬而未决)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 【C++】—— C++11新特性之 “右值引用和移动语义”

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

    2024年02月11日
    浏览(40)
  • 【重学C++】05 | 说透右值引用、移动语义、完美转发(下)

    【重学C++】05 | 说透右值引用、移动语义、完美转发(下) 大家好,我是只讲技术干货的会玩code,今天是【重学C++】的第五讲,在第四讲《【重学C++】04 | 说透右值引用、移动语义、完美转发(上)》中,我们解释了右值和右值引用的相关概念,并介绍了C++的移动语义以及如

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

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

    2023年04月08日
    浏览(46)
  • [开发语言][c++]:左值、右值、左值引用、右值引用和std::move()

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

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

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

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

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

    2023年04月26日
    浏览(37)
  • C++ 学习系列 1 -- 左值、右值与万能引用

    简单的说,左值可以放在等号的左边,右值可以放在等号的右边。 左值可以取地址,右值不能取地址。 1.1 左值举例: 变量、函数或数据成员 返回左值引用的表达式 如 ++x、x = 1、cout \\\' \\\'  int x = 0 1.2 右值举例: 返回非引用类型的表达式 如 x++、x + 1 除字符串字面量之外的字面

    2024年02月14日
    浏览(41)
  • MordernC++之左值(引用)与右值(引用)

    C++中左值与右值的概念是从C中继承而来,一种简单的定义是 左值能够出现再表达式的左边或者右边,而右值只能出现在表达式的右边 。 另一种区分左值和右值的方法是: 有名字、能取地址的值是左值,没有名字、不能取地址的值是右值 。比如上述语句中a,b, c是变量可以

    2023年04月09日
    浏览(38)
  • 【C++11】左值引用和右值引用

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。     目录 一、新的类功能 1、新的默认成员函数 2、类成员变量初始化 3、强制生成默认函数的default 4、禁止生成默认函

    2023年04月17日
    浏览(45)
  • 【送书】【C++11】左值引用和右值引用

    需要云服务器等云产品来学习Linux的同学可以移步/--腾讯云--/--阿里云--/--华为云--/官网,轻量型云服务器低至112元/年,新用户首次下单享超低折扣。     目录 一、新的类功能 1、新的默认成员函数 2、类成员变量初始化 3、强制生成默认函数的default 4、禁止生成默认函

    2023年04月09日
    浏览(36)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包