C++对象模型(15)-- 构造函数语义学:移动构造函数和移动赋值运算符

1、什么是移动构造函数

我们知道拷贝构造函数分为浅拷贝和深拷贝。

(1)浅拷贝:当类含有指针变量时,浅拷贝会发生错误。

(2)深拷贝:每次都要全部赋值一份,内存消耗比较大。

移动构造函数就是为了解决拷贝构造函数存在的问题。移动构造函数将对象的状态或者所有权从一个对象转移到另一个对象,没有内存的搬迁或者内存拷贝,所以可以提高效率。

移动构造函数跟拷贝构造函数的几点不同:

(1)移动构造函数没有给成员变量新申请内存,而是直接将原来对象的值移动到新对象。

(2)移动后原对象的值都归属于新对象,原对象的指针设为NULL;因为移动后原对象会被析构,而NULL可以被多次free,所以这样做比较安全。

(3)移动构造函数的形参不能是const,因为移动后要修改原对象的成员变量指向NULL。

(4)移动构造函数多了一步判断:判断当前指针与输入的右值地址是否相同。

这是为了防止把自身作为输入进行移动赋值,这样会导致得到的对象成员指向NULL。

例子:

(1)移动构造函数:

class A {
public:
    A() {
        num = (int*)malloc(sizeof(int));
        *num = 2;
    }

    A(const A& x) {
        this->num = new int(*x.num);
        cout << "拷贝构造函数" << endl;
    }

    A(A&& x) {
        if (this != &x) {
            this->num = x.num;
            x.num = nullptr;
            cout << "移动构造函数" << endl;
        }
    }

    ~A() {
        free(num);
    }

    int* num;
};

int main()
{
    A a;
    A b = a; //拷贝构造

    A c(move(a)); //移动构造, move()函数把a转换成右值

    if (a.num == nullptr) { //对象a的num已经为nullptr
        std::cout << "a.num is null" << std::endl;
    }

    return 0;
}

(2)移动赋值运算符:

class A {
public:
    A() {
        num = (int*)malloc(sizeof(int));
        *num = 2;
    }

    void operator = (const A& x) {
        this->num = new int(*x.num);
        cout << "拷贝赋值函数" << endl;
    }

    void operator = (A&& x) {
        if (this != &x) {
            this->num = x.num;
            x.num = nullptr;
            cout << "移动赋值函数" << endl;
        }
    }

    ~A() {
        free(num);
    }

    int* num;
};

int main()
{
    A a;

    A b; //注意跟拷贝构造的不同,拷贝赋值时b对象先生成,后再赋值
    b = a; //拷贝赋值

    A c;
    c = move(a); //移动赋值

    return 0;
}

2、移动构造函数和移动赋值运算符的规则:

关于移动构造函数和移动赋值运算符,有下面几条规则:

(1)如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或者析构函数,则编译器就不会生成默认移动构造函数和移动赋值运算符。因为只要类自己有复制对象或者释放对象的操作,编译器就不会帮忙合成移动动作的相关函数,这样可以防止编译器生成不是程序员想要的移动构造函数或移动赋值运算符。

(2)只有1个类没有定义自己的拷贝构造函数、拷贝赋值运算符、析构函数,且类的每个非静态成员都可以被move时,编译器才会为该类合成移动构造函数或移动赋值运算符。

什么叫成员可被move呢?

(1)基础数据类型。如int、byte等。

(2)如果成员变量是一个类,但该类有移动相关的函数。

为了加深对规则的理解,我们来看一个例子:

class MyDemo {
public:
    int m_i;
    string s;

    //~MyDemo(){}
};

int main()
{
    MyDemo demoA;
    demoA.m_i = 2;
    demoA.s = "Hello World";
    cout << "\n before move, demoA.s = " << demoA.s << " , demoA.m_i=" << demoA.m_i << endl;

    MyDemo demoB = std::move(demoA);
    cout << " after move, demoA.s = " << demoA.s << " , demoA.m_i=" << demoA.m_i << endl;

    return 0;
}

代码的执行结果如下:

为什么demoA.s的值在move后,变成空了呢?难道被move走了?

其实这是执行了string类移动构造函数的结果。

如果往类MyDemo添加一个析构函数,再看执行结果:

发现这时demoA.s的值保持不变。

这是因为加了析构函数后,编译器就不会为类MyDemo生成默认移动构造函数了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
拷贝构造函数是用来创建一个新对象并将其初始化为给定对象的副本的特殊成员函数。它通常用于以下情况: - 当一个对象通过值传递给函数或以值的形式返回时 - 当一个对象用另一个对象进行初始化时 - 当一个对象作为另一个对象的成员进行初始化时 对于类`Person`的拷贝构造函数,它会接受一个`const Person&`类型的参数,并将其成员变量`name_`赋值给新创建的对象的`name_`成员变量。 赋值运算符是用于将一个对象的值分配给另一个已经存在的对象的成员函数。它通常用于以下情况: - 当一个对象被另一个对象赋值时 - 当一个对象作为另一个对象的成员进行赋值时 对于类`Person`的赋值运算符,它会接受一个`const Person&`类型的参数,并将其成员变量`name_`赋值给当前对象的`name_`成员变量。然后,它将返回一个指向左侧运算对象的引用,以支持连续赋值的操作。 如果在类定义中没有显式定义拷贝构造函数赋值运算符,编译器会为类生成默认的拷贝构造函数赋值运算符。此外,我们还可以使用`=default`来显式要求编译器生成合成的拷贝构造函数赋值运算符。这将使用默认的实现来完成拷贝和赋值操作。 总之,拷贝构造函数用于创建一个对象的副本,而赋值运算符用于将一个对象的值赋给另一个已经存在的对象。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C++拷贝构造函数与拷贝赋值运算符](https://blog.csdn.net/xiongya8888/article/details/89424224)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值