【C++11保姆级教程】移动构造函数(move constructor)和移动赋值操作符(move assignment operator)

简介: 【C++11保姆级教程】移动构造函数(move constructor)和移动赋值操作符(move assignment operator)

前言


在C++11标准中引入了移动语义的概念,通过移动构造函数和移动赋值操作符,我们可以更高效地管理对象的资源。本文将以通俗易懂的方式详细解释移动构造函数和移动赋值操作符的概念,并通过生动的比喻帮助读者更好地理解这两个概念。


一、移动构造函数(Move Constructor)


1.1 移动构造函数是什么?

移动构造函数是一个特殊的构造函数,它能够从一个右值引用(rvalue reference)创建新的对象,而无需进行深拷贝(deep copy)。我们可以将移动构造函数比喻成搬家时的快递员。


假设你搬家,有一堆家具需要装进卡车。传统的深拷贝(复制构造函数)就像是你把每一件家具都精心地复制一份,然后放进卡车上。这个过程费时费力,而且你原本的家具还要保留。


但是,如果你找来一位勇敢的快递员(移动构造函数),他们可以直接将你的家具移动到新的屋子里,而不用复制。这样,节省了时间和精力,而且你原本的家具可以顺利放进新的屋子。


在代码中,移动构造函数使用右值引用作为参数,并且我们将原始对象的资源直接转移到新对象中,而不是进行复制。这就如同快递员将家具从旧的房子搬到新的房子中,节省了时间和空间开销。


总的来说:可以理解为快速的拷贝构造函数


1.2 基本格式

在构造函数后面写noexcept

ClassName(ClassName&& other) noexcept
{
    // Move the resources from 'other' to the new object
    // ...
}


1.3 示例代码

#include <iostream>
class MyObject {
private:
    int* data;
public:
    MyObject() : data(nullptr) {
        std::cout << "Default Constructor" << std::endl;
    }
    MyObject(int value) : data(new int(value)) {
        std::cout << "Regular Constructor" << std::endl;
    }
    // 移动构造函数
    MyObject(MyObject&& other)noexcept : data(other.data) {
        other.data = nullptr;
        std::cout << "Move Constructor" << std::endl;
    }
    ~MyObject() {
        delete data;
        std::cout << "Destructor" << std::endl;
    }
    void printData() const {
        if (data != nullptr) {
            std::cout << "Data: " << *data << std::endl;
        } else {
            std::cout << "Data is null" << std::endl;
        }
    }
};
int main() {
    MyObject obj1(10);
    obj1.printData();
    MyObject obj2(std::move(obj1));  // 使用std::move调用移动构造函数
    obj2.printData();
    obj1.printData();  // obj1的data现在为null
    return 0;
}


1.4 输出结果

Regular Constructor
Data: 10
Move Constructor
Data: 10
Data is null
Destructor
Destructor

befd64bf89024fc6a48f686b26b61b9e.png


二、移动赋值操作符(Move Assignment Operator)


2.1 移动赋值操作符是什么?

移动赋值操作符允许我们将一个对象的资源转移到另一个对象上。可以把移动赋值操作符比喻成两个人交换工作岗位。


想象一下,你在一家公司工作,有一天你被调往另外一个部门。传统的方式是,你将自己的工作内容复制一份,再将新工作的内容复制回来,形成了两份一样的工作内容。这样的操作显然很冗余。


然而,通过移动赋值操作符,你可以直接将自己的工作内容交给新的员工,并且接管他们原本的工作,省去了不必要的复制步骤。


在代码中,移动赋值操作符也使用右值引用作为参数。我们将源对象的资源直接转移到目标对象中,同时将源对象恢复到一种可安全销毁或重新赋值的状态。这就如同两个人交换工作岗位,互相拥有对方的资源和责任。


总的来说:可以理解为快速的赋值运算符。(比一般的更快,因为无需深拷贝)


2.2 一般格式

ClassName& operator=(ClassName&& other) noexcept
{
    // Move the resources from 'other' to the current object
    // ...
    return *this;
}


示例代码:

#include <iostream>
class MyObject {
private:
    int* data;
public:
    MyObject() : data(nullptr) {
        std::cout << "Default Constructor" << std::endl;
    }
    MyObject(int value) : data(new int(value)) {
        std::cout << "Regular Constructor" << std::endl;
    }
    // 移动构造函数
    MyObject(MyObject&& other) noexcept : data(other.data) {
        other.data = nullptr;
        std::cout << "Move Constructor" << std::endl;
    }
    // 移动赋值操作符
    MyObject& operator=(MyObject&& other) noexcept {
        if (this != &other) {
            delete data;
            data = other.data;
            other.data = nullptr;
        }
        std::cout << "Move Assignment Operator" << std::endl;
        return *this;
    }
    ~MyObject() {
        delete data;
        std::cout << "Destructor" << std::endl;
    }
    void printData() const {
        if (data != nullptr) {
            std::cout << "Data: " << *data << std::endl;
        } else {
            std::cout << "Data is null" << std::endl;
        }
    }
};
int main() {
    MyObject obj1(10);
    obj1.printData();
    MyObject obj2;
    obj2 = std::move(obj1);  // 使用std::move调用移动赋值操作符
    obj2.printData();
    obj1.printData();  // obj1的data现在为null
    return 0;
}


2.3 输出

Regular Constructor
Data: 10
Default Constructor
Move Assignment Operator
Data: 10
Data is null
Destructor


33f675b5d7264348953826287ea4074c.png


总结


当我们需要在C++中处理大量数据或动态分配的对象时,移动构造函数和移动赋值操作符成为了重要的工具。它们是C++11引入的特性,旨在提高程序的性能和效率。

移动构造函数(move constructor)和移动赋值操作符(move assignment operator)的作用是允许将临时对象或资源所有权从一个对象转移给另一个对象,而无需执行深层的数据拷贝和分配新资源。相比复制构造函数和复制赋值操作符,移动操作通常更加高效,因为它只需要重新指定资源的所有权关系,而不需要执行资源的复制或分配。

移动构造函数的语法如下:

类名(类名&amp;&amp; other) noexcept
{
    // 进行资源所有权的转移
}


移动赋值操作符的语法如下:

类名&amp; operator=(类名&amp;&amp; other) noexcept
{
    if (this != &amp;other) {
        // 进行资源所有权的转移
    }
    return *this;
}


在移动构造函数和移动赋值操作符中,我们通过使用右值引用(&&)来标识移动语义,并使用std::move()函数将对象转换为右值。

使用移动构造函数和移动赋值操作符的好处包括:


1.减少不必要的数据拷贝和资源分配,提高程序的性能和效率。

2.在处理大型对象或大量数据时,减少内存的占用和提高程序的响应速度。

3.支持对不可拷贝的对象进行移动操作,使得这些对象也可以被移动和管理。


在使用移动操作时,需要注意以下几点:


4.移动构造函数和移动赋值操作符通常会将被移动对象的资源指针设置为nullptr,以避免资源的重复释放。

5.移动构造函数和移动赋值操作符通常应该具有noexcept规定,表示它们不会抛出异常。

6.移动操作并不会自动删除或释放资源,只是转移资源的所有权关系。移动后的对象需要负责管理和释放资源。移动后的源对象状态通常不可预测,应当谨慎使用。

7.使用移动操作时,对象的移后状态应该仍然是有效且可用的。


总而言之,移动构造函数和移动赋值操作符是C++11引入的重要特性,通过资源所有权的转移而不是数据的拷贝,提高了程序的性能和效率。对于处理大量数据或动态分配的对象,使用移动操作可以显著减少资源的复制和分配,从而提升程序的效率。然而,在使用移动操作时需要注意管理资源的责任,并确保对象的移后状态仍然有效和可用。

相关文章
|
3月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
156 5
|
3月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
170 4
|
4月前
|
C++
C++构造函数初始化类对象
C++构造函数初始化类对象
30 0
|
4月前
|
C++
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
45 0
|
2天前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
1月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
68 19
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
50 13
|
1月前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
50 5
|
1月前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
40 5
|
1月前
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
48 4