【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引入的重要特性,通过资源所有权的转移而不是数据的拷贝,提高了程序的性能和效率。对于处理大量数据或动态分配的对象,使用移动操作可以显著减少资源的复制和分配,从而提升程序的效率。然而,在使用移动操作时需要注意管理资源的责任,并确保对象的移后状态仍然有效和可用。

相关文章
|
1天前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
39 30
|
15天前
|
C++
C++(十)operator=
本文档介绍了 C++ 中赋值运算符 `operator=` 的重载方法,包括其概念、语法格式及特性,并通过实现一个 `mystring` 类展示了具体的代码示例。赋值运算符用于将一个已创建的对象赋值给另一个已创建的对象,需注意自赋值、内存泄漏和重析构等问题。文档中的 `mystring` 类实现了字符串对象的赋值、拼接及比较等功能。
|
1月前
|
编译器 C++
C++的基类和派生类构造函数
基类的成员函数可以被继承,可以通过派生类的对象访问,但这仅仅指的是普通的成员函数,类的构造函数不能被继承。构造函数不能被继承是有道理的,因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。 在设计派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是大部分基类都有 private 属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。 这种矛盾在C++继承中是普遍存在的,解决这个问题的思路是:在派生类的构造函数中调用基类的构造函数。 下面的例子展示了如何在派生类的构造函数中调用基类的构造函数:
23 1
|
29天前
|
存储 C++
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
30 0
|
2月前
|
NoSQL 编译器 Redis
c++开发redis module问题之如果Redis加载了多个C++编写的模块,并且它们都重载了operator new,会有什么影响
c++开发redis module问题之如果Redis加载了多个C++编写的模块,并且它们都重载了operator new,会有什么影响
|
2月前
|
NoSQL Redis C++
c++开发redis module问题之避免多个C++模块之间因重载operator new而产生的冲突,如何解决
c++开发redis module问题之避免多个C++模块之间因重载operator new而产生的冲突,如何解决
|
2月前
|
编译器 C++
【C++】详解运算符重载,赋值运算符重载,++运算符重载
【C++】详解运算符重载,赋值运算符重载,++运算符重载
|
2月前
|
编译器 C++
【C++】详解构造函数
【C++】详解构造函数
|
3月前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。
|
3月前
|
编译器 C++
【C++】类和对象③(类的默认成员函数:赋值运算符重载)
在C++中,运算符重载允许为用户定义的类型扩展运算符功能,但不能创建新运算符如`operator@`。重载的运算符必须至少有一个类类型参数,且不能改变内置类型运算符的含义。`.*::sizeof?`不可重载。赋值运算符`=`通常作为成员函数重载,确保封装性,如`Date`类的`operator==`。赋值运算符应返回引用并检查自我赋值。当未显式重载时,编译器提供默认实现,但这可能不足以处理资源管理。拷贝构造和赋值运算符在对象复制中有不同用途,需根据类需求定制实现。正确实现它们对避免数据错误和内存问题至关重要。接下来将探讨更多操作符重载和默认成员函数。