C++入门——“C++11-右值引用和移动语义”

news2024/11/22 22:30:33

        C++11相比于C++98增加以许多新特性,让C++语言更加灵活好用,但是貌似也增加了许多学习的难度,现在先看第一部分。

        一、右值引用和移动语义

        1.右值引用和左值引用

        在C++中,值可以大致分为右值和左值,左值大概是哪些已经被定义的变量或者对象,它一般具有持久性,它可以出现在赋值符号的左边,也可以出现在赋值符号的右边;右值一般指的是临时变量、字面常量等等。一般来说,区分左右值的方法可以用是否可以取地址来判断:左值可以取地址,右值不可以被取地址。

        下面让我们简单看一段代码吧:

void Func(int& a)
{
	cout << "左值引用" << endl;
}

void Func(int&& a)
{
	cout << "右值引用" << endl;
}

int main()
{
	int a = 1;

	Func(1);
	Func(a);


	return 0;
}

        其中,a是一个普通变量,为左值,而1是一个字面常量,为右值,那么最后的输出结果因该是“右值引用”、“左值引用”。让我们看一看结果:

       2.右值引用引用左值和左值引用引用右值

        右值可以被左值引用引用,因为右值一般具有常性,所以可以通过在左值引用前加上“const”来使得左值引用可以引用右值;而右值引用引用左值则需要使用“move”函数来改变左值的属性。

int main()
{
	int a = 2;

	const int& L = 1;

	int&& R = move(a);

	cout << L << endl << R << endl;

	return 0;
}

3.右值引用存在的意义

a.延长临时对象的生命周期

        右值引用可以延长临时对象的生命周期比如:

int main()
{

	string("123456789");

	return 0;
}

        此代码中,string的生命周期仅限于这一行,倘若使用右值引用来引用,那么就可以延长它的生命周期:

int main()
{

	string&& S = string("123456789");


	cout << S << endl;

	return 0;
}

        同样的,表达式相加的临时对象,函数返回时的临时对象,都可以使用右值引用来延长它的生命周期。

b.移动语义

        当然了,右值引用存在的意义可不是为了简单的延长生命周期,而是为了转移临时对象的数据,这使得数据的转移更加高效和安全,这也正是移动语义的的机制。在此之前还需要了解的是虽然右值是不可以被改变,但是右值引用是具有左值属性的,也就是说,被右值引用引用的右值是可以被修改的。

        拿拷贝构造来说,我们会将它的它的参数写为const类型的,在保证不修改实参的情况下,还能够接收右值。但是无论是左值还是右值,在有资源的情况下需要进行大量的复制行为,特别是在右值的情况下(因为右值的生命周期即将结束,还得进行一次复制,这样会造成效率的低下)。为了解决这个问题,这个时候就需要介绍一下移动构造和移动赋值了。

        移动构造和移动赋值旨在将临时对象(右值)的资源转移到我们的类中,由于右值的生命周期即将结束,秉承着趁你病要你命的原则,我是可以在虚弱的时候掠夺你的资源。这个时候我就可以直接把我的没用的东西和你的资源进行交换,这样做的效率可不是一般的高,因为避免了大量的数据拷贝。

        移动构造和移动赋值同普通的构造和赋值函数一样,只是参数变为了右值,并且函数内部进行资源的互换。

        就用我们的自己写的string类为例。(此处在linux环境下测试,因为VS的编译器会进行优化)

#pragma once
#include<iostream>

using namespace std;

namespace Mynamespace
{
    class string
    {

        friend ostream& operator<<(ostream& _cout, const Mynamespace::string& s);

        friend istream& operator>>(istream& _cin, Mynamespace::string& s);

    public:
        
        typedef char* iterator;

    public:

        string(const char* str = "")
        {
            _str = new char[strlen(str)+1];
            char* der = _str;
            const char* sour = str;
            while (*sour != '\0')
            {
                *(der++) = *(sour++);
            }
            *der = '\0';

            _size = strlen(str);
            _capacity = _size;
        }

        string(const string& s)
        {

            _str = new char[s._capacity];
            char* der = _str;
            const char* sour = s._str;
            while (*sour != '\0')
            {
                *(der++) = *(sour++);
            }
            *der = '\0';
            _size = s._size;
            _capacity = s._capacity;
        }

        string(string&&  s)
        {
            cout << "string(string&& s) 移动构造" << endl;
            swap(s);
        }

        


        ~string()
        {
            delete[] _str;
            _capacity = 0;
            _size = 0;
        }

        string& operator=(const string& s)
        {
            delete[] _str;

            _str = new char[strlen(s._str) + 1];
            char* der = _str;
            const char* sour = s._str;
            while (*sour != '\0')
            {
                *(der++) = *(sour++);
            }
            *der = '\0';

            _size = s._size;
            _capacity = s._capacity;

            return *this;
        }


        // 移动赋值
         string& operator=(string&& s)
        {
            cout << "string& operator=(string&& s) -- 移动赋值" << endl;
            swap(s);
            return *this;
        }


        //

        // iterator
        typedef char* iterator;

        iterator begin()
        {
            return _str;
        }

        iterator end()
        {
            return _str + _size;
        }



        /

        // modify

        void push_back(char c);

        string& operator+=(char c);

        void append(const char* str);

        string& operator+=(const char* str);

        void clear();

        void swap(string& s);

        const char* c_str()const;



        /

        // capacity

        size_t size()const;

        size_t capacity()const;

        bool empty()const;

        void resize(size_t n, char c = '\0');

        void reserve(size_t n);



        /

        // access

        char& operator[](size_t index);

        const char& operator[](size_t index)const;



        /

        //relational operators

        bool operator<(const string& s)
        {
            return strcmp(_str, s._str) < 0;
        }

        bool operator<=(const string& s)
        {
            return *this < s || *this == s;
        }

        bool operator>(const string& s)
        {
            return !(*this <= s);
        }

        bool operator>=(const string& s)
        {
            return !(*this < s);
        }

        bool operator==(const string& s)
        {
            return strcmp(_str, s._str) == 0;
        }

        bool operator!=(const string& s)
        {
            return !(*this == s);
        }



        // 返回c在string中第一次出现的位置

        size_t find(char c, size_t pos = 0) const;

        // 返回子串s在string中第一次出现的位置

        size_t find(const char* s, size_t pos = 0) const;

        // 在pos位置上插入字符c/字符串str,并返回该字符的位置

        string& insert(size_t pos, char c);

        string& insert(size_t pos, const char* str);



        // 删除pos位置上的元素,并返回该元素的下一个位置

        string& erase(size_t pos, size_t len);

    private:

        char* _str;

        size_t _capacity;

        size_t _size;
        
        static const size_t npos = -1;
    };
}
int main()
{
	Mynamespace::string mystring1 = Mynamespace::string("123456789");

	return 0;
}

        可能是编译器的版本的问题,在我的linux环境中,以上代码即使关闭了优化,也仍然进行直接构造而不是构造+移动构造:

cx@Ubuntu-Linux:~/study$ g++ test.cpp -fno-elide-constructors
cx@Ubuntu-Linux:~/study$ ./a.out
 string(const char* str )

        有条件的小伙伴可以自己试一试,其中“-fno-elide-constructors”或者“-O0”可以关闭优化。

所以这里先测试一下移动赋值:

int main()
{


	Mynamespace::string mystring1;

	mystring1  = Mynamespace::string("123456789");

	return 0;
}
cx@Ubuntu-Linux:~/study$ g++ test.cpp -fno-elide-constructors
cx@Ubuntu-Linux:~/study$ ./a.out
 string(const char* str )
 string(const char* str )
string& operator=(string&& s)

          可以看到最后调用了一次移动赋值。

二、引用折叠

        在C++11中,新增加了引用折叠的特性,引用折叠针对的是引用的引用,但是不可以显式的写出来引用的引用,比如:



int main()
{
    int a;
    int&& & b = a;
	return 0;
}

        但是可以隐式的引用(typedef后的引用):

int main()
{
	typedef int& L;
	typedef int&& R;
	int a;

	L& b = a;
	R& c = a;
    R&& d = 1;
    L&& e = a;

	return 0;
}

        引用折叠理解起来有点像与门,在这里,左值引用代表着0,右值引用代表着1,当左值引用右值引用同时存在的时候就是左值引用,只有当两个引用都为右值的时候才为右值引用。

        就拿以上代码为例    L& b = a; 里,b的类型为int&,    R& c = a; 里,c的类型为int&,R&& d = 1;  里,d 的类型为int&&,    L&& e = a; 里 e为左值。

        1.万能引用

        引用折叠的用途在哪里呢?实际上,它可以用来在函数模板中实现万能引用。请看以下函数模板:

template <class T>
void func(T&& n)
{
	cout << "void func(int&& n)" << endl;
}
int main()
{
	int a = 1;
	func(1);
	func(a);


	return 0;
}

        已知字面常量1是一个右值,变量a是一个左值,由于引用折叠,第一次调用的是右值引用版本的func,第二次是左值引用的func。最后的运行结果如下:

        这样可以用一个模板实现左值引用和右值引用的两个版本。

        2.完美转发

        先看以下代码:

void F(int& a)
{
	cout << "左值引用" << endl;

}
void F(int&& a)
{
	cout << "右值引用" << endl;

}
void F(const int& a)
{
	cout << "左值引用" << endl;
}

template <class T>
void func(T&& n)
{
	F(n);
}


int main()
{
	int a = 1;
	

	func(a);
	func(2);


	return 0;
}

        我们想要的输出结果是“左值引用”、“右值引用”,那么先看一下运行结果:

        哎。为什么是两个左值引用呢?

        由于右值引用的属性为左值,当我们想连续传递一个右值的时候,在第一次传递后,这个值就已经变为左值属性了,为了解决这个问题,C++11引入了完美转发,它的目得是为了在右值连续传递的过程中不改变右值的属性。

        完美转发的本质是一个函数模板forward,它的底层是一个强转(左值引用和右值引用的本质还是指针,只是在语义上不同)。它的用法是在传递参数的地方加上模板的类型,后边紧接着参数是传递的值:

void F(int& a)
{
	cout << "左值引用" << endl;

}
void F(int&& a)
{
	cout << "右值引用" << endl;

}
void F(const int& a)
{
	cout << "左值引用" << endl;
}

template <class T>
void func(T&& n)
{
	F(forward<T>(n));
}


int main()
{
	int a = 1;
	

	func(a);
	func(2);


	return 0;
}

        运行结果:

        我们可以分析一下:

        a.传递的参数为a的时候,a为左值,func发生了引用折叠,编译器推导出T为int&,那么forward强转n为左值并返回。

        b.传递的参数为字面常量1的时候,1为右值,func没有发生引用折叠,编译器推导出T为int&&,那么forward强转n为右值并返回。

        总的来说,完美转发就是根据T的类型来推导最后返回的类型,如果T是右值,那么最后返回的就是右值属性的对象,如果T为左值,那么最后返回的就是左值属性的对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2230172.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode 64. 最小路径和 动态规划+空间优化

原题链接&#xff1a;Leetcode 64. 最小路径和 二维数据 class Solution { public:int minPathSum(vector<vector<int>>& grid) {int m grid.size();int n grid[0].size();int dp[m][n];dp[0][0] grid[0][0];for (int j 1; j < n; j)dp[0][j] dp[0][…

OpenHarmony、HarmonyOS、HarmonyNext互相兼容吗?

1&#xff0c;三者之间的关系 OpenHarmony&#xff1a;开源底层。HarmonyOS&#xff1a;闭源手机系统&#xff0c;兼容安卓生态。HarmonyOS NEXT&#xff1a;纯血鸿蒙&#xff0c;不兼容安卓。 上一篇文章简单介绍过&#xff0c;就不再多说了&#xff0c;这里说一下HarmonyOS …

定制化视频生成新模范!零样本主体驱动,精确运动控制!复旦阿里等发布DreamVideo-2

文章链接&#xff1a;https://arxiv.org/pdf/2410.13830 项目链接&#xff1a;https://dreamvideo2.github.io/ 亮点直击 DreamVideo-2&#xff0c;首个无需微调&#xff0c;同时支持主体定制和运动控制的零样本视频定制框架&#xff0c;能够通过设计的参考注意力学习主体外观&…

Java中的线程安全问题(如果想知道Java中有关线程安全问题的基本知识,那么只看这一篇就足够了!)

前言&#xff1a;多线程编程已经广泛开始使用&#xff0c;其可以充分利用系统资源来提升效率&#xff0c;但是线程安全问题也随之出现&#xff0c;它直接影响了程序的正确性和稳定性&#xff0c;需要对其进行深入的理解与解决。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解…

Kubernetes Node 节点的生命周期简述

Node 节点是 Kubernetes 的核心组件之一&#xff0c;其生命周期可以简要概括为&#xff1a;注册、运行、下线。本文将简要介绍 Node 生命周期中发生的关键事件。 节点注册 每个 node 节点都需要运行 kubelet&#xff0c;kubelet 启动后会向 kube-apiserver 发起节点的注册请求…

GenAI 生态系统现状:不止大语言模型和向量数据库

自 20 个月前 ChatGPT 革命性的推出以来&#xff0c;生成式人工智能&#xff08;GenAI&#xff09;领域经历了显著的发展和创新。最初&#xff0c;大语言模型&#xff08;LLMs&#xff09;和向量数据库吸引了最多的关注。然而&#xff0c;GenAI 生态系统远不止这两个部分&#…

ctf文件上传题小总结与记录

解题思路&#xff1a;先看中间件&#xff0c;文件上传点&#xff08;字典扫描&#xff0c;会员中心&#xff09;&#xff0c;绕过/验证&#xff08;黑名单&#xff0c;白名单&#xff09;&#xff0c;解析漏洞&#xff0c;cms&#xff0c;编辑器&#xff0c;最新cve 文件上传漏…

如何使用VBA识别Excel中的“单元格中的图片”(2/2)

Excel 365升级了新功能&#xff0c;支持两种不同的插入图片方式&#xff1a; 放置在单元格中&#xff08;Place in cell&#xff09;&#xff0c;新功能&#xff0c;此操作插入的图片下文中简称为单元格中的图片。放置在单元格上&#xff08;Place over cell&#xff09;&…

python绘制置信椭圆

置信椭圆 置信椭圆&#xff08;Confidence Ellipse&#xff09;是一种用来表示二维数据的置信区域的统计工具。它是多元统计分析中常用的图形&#xff0c;通过它可以表示两个变量之间的协方差关系以及同时包含两个变量的可能取值范围。置信椭圆一般用于描述一组二维数据的散布…

用Python设置、更新和获取Excel单元格的值

Excel工作簿作为一款广泛使用的数据管理工具&#xff0c;与Python相结合&#xff0c;可以使得自动化处理大量数据成为可能。通过Python来设置、更新以及读取Excel单元格的值&#xff0c;不仅可以极大地提高工作效率&#xff0c;减少重复劳动&#xff0c;还能增强数据处理流程的…

轴承故障全家桶更新 | 基于时频图像的分类算法

往期精彩内容&#xff1a; Python-凯斯西储大学&#xff08;CWRU&#xff09;轴承数据解读与分类处理 Pytorch-LSTM轴承故障一维信号分类(一)-CSDN博客 Pytorch-CNN轴承故障一维信号分类(二)-CSDN博客 Pytorch-Transformer轴承故障一维信号分类(三)-CSDN博客 三十多个开源…

001-Kotlin界面开发之Jetpack Compose Desktop学习路径

Compose Desktop学习之路 学习过程 理解Kotlin的基本语法 Compose Desktop采用Kotlin构建&#xff0c;因此对Kotlin的基本语法有很好的理解是必不可少的。你可以从官方的Kotlin文档开始。 用一句话概括&#xff0c;Kotlin是一种现代的、静态类型的编程语言&#xff0c;它结合…

阳振坤:云时代数据库的思考 | OceanBase发布会实录

在2024 OceanBase 年度发布会中&#xff0c;OceanBase 的创始人与首席科学家阳振坤进行了《云时代数据库的思考》的主题分享。本文为演讲实录。 亲爱的朋友们&#xff0c;衷心感谢各位莅临今天的发布会现场。今天是一个云的时代&#xff0c;我想与大家分享&#xff0c;我对于云…

JavaScript语法基础(函数,对象,常用类Array,String,Math和Date)【超详细!!!新手入!!!】

一、函数 1、函数的定义 函数指的是一段可被重复调用的代码块。函数与变量不同&#xff0c;需要先定义再调用。 定义函数的语法格式为&#xff1a; function 函数名&#xff08;参数1&#xff0c;参数2&#xff0c;...&#xff09; { 语句&#xff1b; …

OpenGL入门004——使用EBO绘制矩形

本节将利用EBO来绘制矩形 文章目录 一些概念EBO 实战简介utilswindowFactory.hRectangleModel.hRectangleModel.cpp main.cppCMakeLists.txt最终效果 一些概念 EBO 概述&#xff1a; Element Buffer Object 用于存储顶点的索引数据&#xff0c;以便在绘制图形时可以重用顶点数…

linux之网络子系统-用户层接收数据包之同步阻塞方案

一、前言 之前讲述了网络包是如何从网卡送到协议栈的&#xff0c;接下来内核还有一项重要的工作&#xff0c;就是在协议栈接收处理完输入包后要通知到用户进程&#xff0c;如何用户进程接收到并处理这些数据。 进程与内核配合有多种方案&#xff0c;这里我们这分析两种典型的…

高效消防应急:RFID技术救援装备的快速管理

基层应急救援消防设施管理面临着一个既复杂又迫切的挑战。通常&#xff0c;受限的资源和专业人才的短缺导致应对突发事件的反应迟缓&#xff0c;处理结果不理想。同时&#xff0c;消防团队的人员结构和技术能力也在一定程度上决定了应急救援的成效和效率。在数字化浪潮下&#…

TMDOG的Gin学习笔记_01——初识Gin框架

TMDOG的Gin学习笔记_01——初识Gin框架 博客地址&#xff1a;[TMDOG的博客](https://blog.tmdog114514.icu) 作者自述&#xff1a; 停更太久了&#xff0c;是因为开学了课太多了&#xff0c;并且我一直在准备上篇文章的内容正在coding&#xff0c;就先搁置了更新博客QAQ&…

【ROS的TF系统】

系列文章目录 TF系统简介 前面的章节实现了SLAM节点的建图功能&#xff1a; 激光雷达节点—> /scan话题 —>hector_mapping节点—> 地图数据话题/map 本期来实现SLAM节点的定位功能&#xff1a; TF&#xff08;TransForm&#xff09;主要描述的是两个坐标系的空间关…

Pandas JSON学习

1.JSON简介 JSON&#xff08;JavaScript Object Notation&#xff0c;JavaScript 对象表示法&#xff09;&#xff0c;是存储和交换文本信息的语法&#xff0c;类似 XML。JSON 比 XML 更小、更快&#xff0c;更易解析&#xff0c;Pandas 可以很方便的处理 JSON 数据。 [{"…