首页 > 其他分享 >27. 右值引用

27. 右值引用

时间:2024-11-16 17:47:13浏览次数:3  
标签:27 右值 对象 int 引用 MyClass 构造函数

一、什么是右值引用

  右值引用是 C++11 中引入的一种重要特性,它主要用于支持移动语义和完美转发。右值引用 是对右值(即临时对象或即将被销毁的对象)的引用,允许我们直接操作这些对象的资源,而无需进行拷贝。在 C++98 中,临时对象(右值)在赋值给函数参数时,只能被接受为 const 引用,这意味着函数无法修改这些对象的值。C++11 引入右值引用后,我们可以使用非常量引用(即右值引用)来接收这些临时对象,并在函数中直接操作它们。

  左值和右值是表达式的分类,它们的主要区别在于表达式的值是否可以取地址。左值是可以取地址的表达式,而右值通常是不能取地址的表达式。

  • 左值(Lvalue):通常有明确存储地址的表达式,如变量、对象的名称等。
  • 右值(Rvalue):通常是没有明确存储地址的表达式,如字面量、临时对象、返回临时对象的表达式等。

  右值引用是对右值的引用,它使用 && 操作符来声明。右值引用的主要目的是为了实现移动语义,即允许资源的所有权从一个对象转移到另一个对象,从而避免不必要的拷贝,提高性能。

#include <iostream>

using namespace std;

int main(void)
{
    int num= 10;            // 左值

    int &a = num;           // 左值引用
    int && b = 30;          // 右值引用
    const int &c = num;     // 常量左值引用
    const int &&d = 50;     // 常量右值引用

    // 左值引用可以使用右值引用初始化
    int &e = b;
    const int &f = b;
    const int &g = d; 

    // 右值引用只能使用右值初始化
  
    return 0;
}

二、右值引用的作用

  右值引用就是对一个右值进行引用的类型。因为右值是匿名的,所以我们只能通过引用方式找到它。无论声明左值引用还是右值引用都必须立即进行初始化,这是因为引用本身并不拥有所绑定对象的内存,它只是该对象的一个别名。通过右值引用的声明,该右值又 “重获新生”,其 生命周期与右值引用类型变量的生命周期一样,只要该变量还存活,该右值临时变量将会一直存活下去。

#include <iostream>

using namespace std;

class MyClass 
{
private:
    int *p;

public:
    MyClass(void);                          // 无参构造函数
    MyClass(const MyClass &other);          // 拷贝构造函数

    ~MyClass(void);                         // 析构函数
};

MyClass::MyClass(void) : p(new int(100))
{
    cout << "Constructor called" << endl;
}

MyClass::MyClass(const MyClass &other) : p(new int(*other.p))
{
    cout << "Copy constructor called" << endl;
}

MyClass::~MyClass(void) 
{
    cout << "Destructor called" << endl;
    delete p;
}

MyClass getMyClass(void) 
{
    MyClass temp;
    return temp;                            // 返回一个临时对象,这是一个右值
}

int main(void) 
{
    MyClass obj = getMyClass();

    return 0;
}

  这里,我们要使用命令 gcc -o template.cpp -fbi-elide-constructors 来手动编译 C++ 程序,使用命令行参数 -fno-elide-constructors 用于关闭函数返回值优化(RVO)。这是因为 GCC 的 RVO 优化会减少复制构造函数的调用。

  运行这段程序,会发现该程序发生三次构造。首先 getMyClass() 函数中 MyClass temp;会调用 无参的构造函数,然后 return temp;会使用 复制构造 产生临时对象,接着 MyClass obj = GetMyClass(); 会使用 复制构造 将临时对象复制到 obj,最后临时对象被销毁。

  但如果将 MyClass obj = getMyClass();替换为 MyClass &&obj = getMyClass(); 使用右值引用后,会调用两次构造函数,一次是 getMyClass() 中 MyClass temp 会调用 无参的构造函数,另一次是 return temp; 会使用 复制构造 产生临时对象。不同的是,由于 obj 是一个右值引用,引用的对象是函数 getMyClass() 返回的临时对象,因此该临时对象的生命周期得到延长,所以我们可以在 MyClass &&obj = getMyClass() 语句结束后继续调用该对象的其它方法而不会发生任何问题。

三、移动语义

  上述代码中 3 次构造函数的调用,不难发现第二次和第三次的复制构造是影响性能的主要原因。在这个过程中都有临时对象参与进来,而临时对象本身只是做数据的复制。如果能将临时对象的内存直接转移到 obj 对象中,就能消除内存复制对性能的消耗。在 C++11 标准中引入了 移动语义,它可以帮助我们将临时对象的内存移动到 obj 对象中,以避免内存数据的复制。

#include <iostream>

using namespace std;

class MyClass 
{
private:
    int *p;

public:
    MyClass(void);                          // 无参构造函数
    MyClass(const MyClass &other);          // 拷贝构造函数
    MyClass(MyClass &&other);               // 移动构造函数

    ~MyClass(void);                         // 析构函数
};

MyClass::MyClass(void) : p(new int(100))
{
    cout << "Constructor called" << endl;
}

MyClass::MyClass(const MyClass &other) : p(new int(*other.p))
{
    cout << "Copy constructor called" << endl;
}

MyClass::MyClass(MyClass &&other) : p(other.p) 
{
    cout << "Move constructor called" << endl;
    other.p = nullptr;
}

MyClass::~MyClass(void) 
{
    cout << "Destructor called" << endl;
    delete p;
}

MyClass getMyClass(void) 
{
    MyClass temp;
    return temp;                            // 返回一个临时对象,这是一个右值
}

int main(void) 
{
    MyClass obj1 = getMyClass();

    return 0;
}

  在上面的代码中 MyClass 类中增加了构造函数 MyClass(MyClass&& other),它的形参是一个 右值引用 类型,称为 移动构造函数

  对于 复制构造函数 而言形参是一个 左值引用,也就是说函数的实参必须是一个具名的 左值,在复制构造函数中往往进行的是 深复制,即在不能破坏实参对象的前提下复制目标对象。而 移动构造函数 恰恰相反,它接受的是一个 右值,其核心思想是通过 转移实参对象的数据 以达成构造目标对象的目的,也就是说实参对象是会被修改的。

  运行程序可以发现,后面两次的构造函数变成了 移动构造函数,因为这两次操作中源对象都是 右值(临时对象),对于右值编译器会优先选择使用移动构造函数去构造目标对象。当移动构造函数不存在的时候才会退而求其次地使用复制构造函数。在移动构造函数中使用了指针转移的方式构造目标对象,所以整个程序的运行效率得到大幅提升。

四、完美转发

  完美转发(Perfect Forwarding)是 C++11 中引入的一个特性,它允许在函数模板中将参数连同其值类别(左值或右值)不变地转发给另一个函数。这意味着如果原始参数是一个左值,它将被转发为左值;如果原始参数是一个右值,它将被转发为右值。完美转发通常涉及到两个函数模板:std::forwardstd::movestd::forward 模板函数用于转发参数,而 std::move 用于将左值转换为右值引用。

#include <iostream>
#include <type_traits>

using namespace std;
void printValue(int& value) 
{
    cout << "Lvalue reference to: " << value << endl;
}

void printValue(int&& value) 
{
    cout << "Rvalue reference to: " << value << endl;
}

template<typename T>
void forwardValue(T &&value) 
{
    printValue(std::forward<T>(value));
}

int main(void) 
{
    int x = 42;

    forwardValue(x);                    // 转发左值
    forwardValue(42);                   // 转发右值
    forwardValue(std::move(x));         // 强制转为右值并转发

    return 0;
}

  在这个示例中,forwardValue 是一个模板函数,它接受一个通用引用参数 T&&。使用 std::forward<T>(value),我们可以将参数 value 连同其值类别完美地转发给 printValue() 函数。这样,printValue() 函数就能根据接收到的参数是左值还是右值来选择正确的重载版本。

标签:27,右值,对象,int,引用,MyClass,构造函数
From: https://blog.csdn.net/flurry_heart/article/details/143820724

相关文章

  • [leetcode]27. 移除元素(Java实现)
    题目给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:更改 nums 数组,使 nums 的前 k ......
  • 计算机毕业设计—12795 Ssm网上考试系统(源码免费领)
    摘 要科技进步的飞速发展引起人们日常生活的巨大变化,电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流,人类发展的历史正进入一个新时代。在现实运用中,应用软件的工作规则和开发步骤,采用SSM框架开发基......
  • 20222327 2024-2025-1 《网络与系统攻防技术》实验六实验报告
    一、实验内容学习掌握了Metasploit工具的使用,具体的操作总结来说就是Search-Use-Show-Set-Exploit/run学习了利用相关漏洞进行模拟攻击的操作,对防范恶意攻击有了一些认识(安装杀软,不要点击陌生网站、文件链接等)二、实验过程1、前期渗透①主机发现(可用Aux中的arp_sweep,search一......
  • (分享源码)计算机毕业设计必看必学 上万套实战教程手把手教学JAVA、PHP,node.js,C++、pyth
    摘要随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,招聘网站当然也不能排除在外。招聘网站是以实际运用为开发背景,运用软件工程开发方法,采用互联网技术构建的一个管理系统。整个开发过程首先对软件系统进行需......
  • 27. 使用MySQL之全球化和本地化
    1.字符集和校对顺序数据库表被用来存储和检索数据。不同的语言和字符集需要以不同的方式存储和检索。因此,MySQL需要适应不同的字符集(不同的字母和字符),适应不同的排序和检索数据的方法。在讨论多种语言和字符集时,将会遇到以下重要术语:字符集为字母和符号的集合;编码为某个......
  • .Net Core关于项目引用和命名空间导入的一个小坑
    .NetCore关于项目引用和命名空间导入的一个小坑一、.NetCore项目嵌套引用的情况经笔者测试验证,发现对于一个.NetCore项目Root,其引用另一个.NetCore项目Root.SubA后,Root项目会自动将Root.SubA项目引用的子项目,也纳入其引用项目池中,而无需再手动引用这些子项目。但这些项目如......
  • P11276 第一首歌 题解
    P11276第一首歌题解一道简单的字符串题目。题目解释说求一个最短的字符串\(t\),使其满足对于给定的字符串\(s\):\(s\)为\(t\)的前缀。\(s\)为\(t\)的后缀。\(s\)不为\(t\)。仔细思考一下,则易得\(t\)的最短长度的最大值为\(s\)的两倍。即当\(s\)......
  • 魅族市场 签名空包 找不到meizu的证书链。meizu必须引用包含私有密钥和相应的公共密
    官方地址:https://open.flyme.cn/docs?id=18详解:1.下载空白 下载后解压得到 jarsigner-verbose-keystore.\tp.keystore-signedjarmeizuemptyapk-release-signed.apkmeizuemptyapk-release-unsigned.apktestalias给apk包签名的方式有很多种,我们推荐您使用JDK自带......
  • Brave127编译指南 Windows篇:部署Node.js(五)
    1.概述在Brave浏览器的编译过程中,Node.js扮演着关键角色。作为一个建立在ChromeV8引擎之上的JavaScript运行时环境,Node.js为开发者提供了在服务器端执行JavaScript代码的能力。它的非阻塞、事件驱动架构使其特别适合构建高性能、可扩展的网络应用。对于Brave浏览器的开发而......
  • 题解:P11277 世界沉睡童话
    比较简单的构造。注意到题面给出\(a_i\le2n-1\)的条件,考虑这个有什么用,你会发现从\(n\)到\(2n-1\)这\(n\)个数都是两两互不为约数,所以当我们构造出序列后,这些数可以用来填补空位。\(k\)的上界是\(\frac{n(n-1)}{2}\),显然在全部都为同一个数的时候取到,显然有\(x\)个......