首页 > 编程语言 >C++--移动构造函数/移动赋值运算符

C++--移动构造函数/移动赋值运算符

时间:2024-06-08 17:11:26浏览次数:16  
标签:tmp 运算符 str Myclass 拷贝 移动 构造函数

C++--移动构造函数/移动赋值运算符

什么是移动语义?

在C++11中,移动语义是一个重要的新特性,它可以使程序在内存管理方面更加高效,同时也提高了程序的性能

它允许将一个对象的所有权从一个对象转移到另一个对象,而不需要进行数据的拷贝。

通俗理解

我有一份材料,A同学找我借,那我把材料复印一份,把复印件给他,这叫做数据拷贝;而我如果把材料的所有权转让给他,那么他现在直接拥有原始的那份材料,这叫做移动语义。

为什么需要移动语义?

拷贝

要理解为什么我们需要移动语义,那我们就需要理解拷贝的操作

C++中有拷贝构造函数和拷贝复制运算符。拷贝,顾名思义就是重新申请一块新的内存空间,然后将需要的数据复制一份放到里面。

如果要复制的对象中涉及到了其他对象或者是指针数据的话,那么拷贝操作就是一项耗时的过程。

我们通过一个例子来演示一下是如果进行拷贝的:

定义一个简单的类:

class Myclass
{
    public:
    Myclass(const std::string& s):str(s){};
    private:
    std::string str;
};

当新建一个对象A时,传递一个参数”hello,world”,A中的成员变量会存储该字符串,也就是会申请一个新的内存空间去存储该字符串:

	Myclass A("hello,world");

当我们定义一个新的对象B,并将A赋值给B时:

	Myclass B=A;

同样,B也会申请一段空间,并将A中存储的字符串拷贝过来

需要移动语义的情况

从拷贝的操作不难看出,这样的操作是耗时的,那在什么情况下,拷贝操作不是必要的呢?

同样还是延续上面的例子,这里我们定义一个容器以及一个对象tmp,然后将其装入到容器中:

std::vector<Myclass> myClasses;
Myclass tmp("hello");
myClasses.push_back(tmp);
myClasses.push_back(tmp);

每次将对象添加到容器中都会发生一次拷贝操作。

我们现在假设tmp对象在被添加到容器中两次之后就不再需要了,那第二次添加的时候是不是可以让容器直接取tmp对象的数据呢?这就是移动语义的意义,减少不必要的数据拷贝,提高程序的性能。

假设Myclass类已经实现了移动语义,我们就可以使用std::move 让myClasses容器直接转移tmp对象的数据为己用。

myClasses.push_back(tmp);
myClasses.push_back(std::move(tmp));

移动语义的实现

我们需要先了解右值引用

右值引用

我们都知道C++有一个操作叫引用,实际上默认指的是左值引用,也就是对一个左值进行引用。那右值引用就是对右值的引用

通过&& 声明,同时:

  1. 右值引用只能绑定到一个右值,不能绑定到左值;
  2. 右值引用可以通过std::move() 将一个左值转换成右值引用
int a=0;
int&& tmp=a;   //error,不能引用左值
int&& tmp2=0;  //correct

移动构造函数

还是接着上面的例子,当向容器添加一个新元素时,如果是通过拷贝的方法,那么对应执行的就是拷贝构造函数,而如果是通过移动的方式,那对应的就是移动构造函数

下面我们就为Myclass定义移动构造函数,其中string类型本身就实现了移动构造函数,所以可以直接使用std::move

Myclass(Myclass&& rValue) noexcept
    :str(std::move(rValue.str))
    {}

在移动构造函数中,我们要做的就是转移成员数据str。

我们就可以使用移动构造函数去创建新的对象而无需拷贝复制了:

Myclass tmp("hello");
Myclass A(std::move(tmp));

如果我们的类成员数据需要我们自己实现数据转移的话,也很简单,就是把数据拿过来,并将原先对象的数据清楚:

假设这个类有两个成员变量int 和 char*类型:

class Myclass2
{
private:
    int data;
    char* str;
public:
    Myclass2()
    :data(30)
    {
        str="mrdc";
    }
    ;
    Myclass2(Myclass2&& rValue) noexcept
    :data(std::move(rValue.data))
    {
        rValue.data=0;  //delete data
        str=rValue.str; //transfer
        rValue.str=nullptr; //delete str
    }
    ~Myclass2()
    {
        if(str!=nullptr)
        {
            delete str;
            str=nullptr;
        }
    };
};

通过移动构造函数创建对象B:

MyClass A{};
MyClass B{ std::move(A) }

内存中的布局:
内存布局

移动赋值运算符

和移动构造函数的实现类似:

// 移动赋值运算符
MyClass& operator=(MyClass&& myClass) noexcept
{
    val = myClass.val;
    myClass.val = 0;
    name = myClass.name;
    myClass.name = nullptr;
    return *this;
}

参考

https://bbs.huaweicloud.com/blogs/375866

标签:tmp,运算符,str,Myclass,拷贝,移动,构造函数
From: https://www.cnblogs.com/Mr-DC/p/18238771

相关文章

  • Java位运算符代码演示
    文章目录原码、反码、补码代码演示(Java)应用参考原码、反码、补码在计算机内,有符号数有3种表示方法:原码、反码和补码,机器数的最高位为符号位,符号位为0表示正数,符号位为1表示负数。原码=符号位+真值的绝对值。如:3的原码(这里假设机器数的字长为8)是00000011,-3的原码是10......
  • 移动游戏的性能优化 | 材质优化篇
     材质是什么虚幻引擎是以hlsl着色语言为基础,来实现vs、ps、cs等,引擎底层提供了一套翻译系统,将hlsl翻译成gpu可执行的代码。 本篇文章限制下两个名词的定义: ● 材质:特指在虚幻引擎材质编辑界面通过连线等方式,生成的材质资源,它是蓝图系统的产物。材质资源会被转成hlsl代码......
  • MainWindows移动View文件夹和使用Window.DataContext单例绑定需要修改的地方
    项目结构移动文件夹后需要修改的三个地方1、App.xaml2、MainWindow.xaml3、MainWindow.xaml.cs单例绑定需要修改的地方MainWindow.xaml ......
  • 在我的 ngrx 效果中,switchMap 和其他运算符的区别
    我有以下效果publicSetProperTab$=createEffect(publicSetProperTab$=createEffect(()=>{返回this.actions$.pipe(ofType(actions.SetProperTab)、switchMap((action)=>;this.store$.select(selectors.GetHasLogicalPr......
  • C++Primer Plus第12章类和动态内存分配--再谈定位new运算符----12.8
    12.5.3再谈定位new运算符本书前面介绍过,定位new运算符让您能够在分配内存时能够指定内存位置。第9章从内置类型的角度讨论了定位new运算符,将这种运算符用于对象时情况有些不同,程序清单12.8使用了定位new运算符和常规new运算符给对象分配内存,其中定义的类的构造函数......
  • C++Primer Plus第12章类和动态内存分配--再谈定位new运算符----12.9
    该程序使用定位new运算符在相邻的内存单元中创建两个对象,并调用了合适的析构函数。#pragmaregion12.9placenew2.cpp//placenew2.cpp--newplacementnew,nodelete#if1#include<iostream>#include<string>#include<new>usingnamespacestd;constintBU......
  • 运算符重载编程基础
    运算符重载编程基础运算符重载的两种方法定义运算符重载函数名的步骤友元函数实现操作符重载的应用场景友元函数和成员函数选择方法用友元函数重载<<>>操作符友元函数重载操作符使用注意点友元函数案例vector类例如://全局函数完成+操作符重载Complexope......
  • 如何使该页面对移动设备友好?
    这是背景徽标。GTA的3张标题图片在768px宽度下无法排成一列,媒体查询将它们设置为只有一列的网格。我以前这样做成功过,但在这里却行不通。.body{margin-top:0pt;margin-left:0pt;margin-right:0pt;margin-bottom:0pt;}.gta{background-image:url(......
  • C++PrimerPlus第十一章类的使用 :练习7 复数类的实现和重载运算符对复数做运算----本
    复数有两个部分组成:实数部分和虚数部分。复数的一种书写方式是:(3.0,4.0),其中,3.0是实数部分,4.0是虚数部分。假设a=(A,Bi),c=(C,Di),则下面是一些复数运算。加法:a+c=(A+C,(B+D)i)。减法:a-c=(A-C,(B-D)i)。乘法:ac=(AC-BD,(AD+B*C)i)。乘法::xc=(xC,x*Di),其中x为实数。......
  • C++PrimerPlus第十一章类的使用 :练习6 关系运算符的重载
    6.重新编写Stonewt类(程序清单11.16和程序清单11.17),重载全部6个关系运算符。运算符对pounds成员进行比较,并返回一个bool值。   编写一个程序,它声明一个包含6个Stonewt对象的数组,并在数组声明中初始化前3个对象。然后使用循环来读取用于设置剩余3个数   组元素的......