【C/C++】【类和对象】对象移动和移动构造函数

对象移动

C++11引入对象移动;进行所有权的转移;

移动构造函数和移动赋值运算符应该完成的功能

  • 确保必要的内存移动,斩断原对象和内存的关系;
  • 确保移动后原对象处于一种“即便被销毁也没有什么问题”的一种状态;A ---> B,确保不再使用A,而是应该去使用B;

移动构造函数

引入目的:提高程序效率;

说明:

  1. A --> B,那么A对象就不能再使用了;

  2. 移动:并不是把内存中的数据从一个地址移动到另一个地址,只是内存所有者变更;

拷贝构造函数

class Temp;
Temp::Temp(const Temp& tmp); //参数是const左值引用

移动构造函数

class Temp;
Temp::Temp(Temp&& tmp); //参数是右值引用
#include <iostream>
using namespace std;

class B
{
public:
    //默认构造函数
    B() :m_b(100) 
    {
        //cout << "类B的构造函数执行了" << endl;
    };
    //拷贝构造函数
    B(const B& tmp) :m_b(tmp.m_b)
    {
        //cout << "类B的拷贝构造函数执行了" << endl;
    };

    virtual ~B()
    {
        //cout << "类B的析构函数执行了" << endl;
    }
public:
    int m_b;
};


class A
{
public:
    A() :m_p_b(new B()) //调用类B的构造函数
    {
        cout << "类A的构造函数执行了" << endl;
    }

    A(const A& tmp) :m_p_b(new B(*(tmp.m_p_b))) //调用类B的拷贝构造函数
    {
        cout << "类A的拷贝构造函数执行了" << endl;
    }
    //C++11 引入 noexcept 通知标准库,移动构造函数不抛出任何异常,提高编译器工作效率;
    //移动构造函数要添加noexcept
    A(A&& tmp) noexcept :m_p_b(tmp.m_p_b) //原来对象a指向的内存m_p_b,直接就让这个临时对象指向这段内存;
    {
        cout << "类A的移动构造函数执行了" << endl;
        tmp.m_p_b = nullptr;
    }

    virtual ~A()
    {
        delete m_p_b;
        cout << "类A的析构函数执行了" << endl;
    }

public:
    B* m_p_b;
};


static A get_A()
{
    A a;
    return a; //生成临时对象 调用拷贝构造函数
}

int main()
{
    //B* p_b = new B(); //调用类B的构造函数;
    //p_b->m_b = 19;

    //B* p_b_1 = new B(*p_b); //调用类B的拷贝构造函数;


    //delete p_b; //析构函数
    //delete p_b_1; //析构函数


    A a = get_A(); //调用1次构造,1次拷贝构造,2次析构,程序结束


    //A中添加移动构造函数后,就调用移动构造函数
    //移动构造函数:把a对象移动给临时对象


    //A a = get_A(); 增加移动构造函数后,调用1次构造,1次移动构造,2次析构,程序结束
    A a_1(a); //1次拷贝构造函数

    //如何让A a_1(a) 调用移动构造函数
    //使用str::move
    A a_2(std::move(a));
    A&& a_2(std::move(a)); //不会产生新对象,不会调用移动构造函数;等同于把对象a有了一个新别名a_2; 后续建议使用a_2操作,不要再使用a;


    A&& aa = get_A(); //从getA返回临时对象被a接管了;//调用1次构造,1次移动构造,2次析构,程序结束
    return 0;
}

移动赋值运算符

#include <iostream>
using namespace std;

class B
{
public:
    //默认构造函数
    B() :m_b(100) 
    {
        //cout << "类B的构造函数执行了" << endl;
    };
    //拷贝构造函数
    B(const B& tmp) :m_b(tmp.m_b)
    {
        //cout << "类B的拷贝构造函数执行了" << endl;
    };

    virtual ~B()
    {
        //cout << "类B的析构函数执行了" << endl;
    }
public:
    int m_b;
};


class A
{
public:
    A() :m_p_b(new B()) //调用类B的构造函数
    {
        cout << "类A的构造函数执行了" << endl;
    }

    A(const A& tmp) :m_p_b(new B(*(tmp.m_p_b))) //调用类B的拷贝构造函数
    {
        cout << "类A的拷贝构造函数执行了" << endl;
    }
    //C++11 引入 noexcept 通知标准库,移动构造函数不抛出任何异常,提高编译器工作效率;
    //移动构造函数要添加noexcept
    A(A&& tmp) noexcept :m_p_b(tmp.m_p_b) //原来对象a指向的内存m_p_b,直接就让这个临时对象指向这段内存;
    {
        cout << "类A的移动构造函数执行了" << endl;
        tmp.m_p_b = nullptr;
    }

    virtual ~A()
    {
        delete m_p_b;
        cout << "类A的析构函数执行了" << endl;
    }
    //拷贝赋值运算符
    A& operator=(const A& src)
    {
        if (this == &src)
            return *this;
        delete m_p_b; //释放自己的这块内存
        m_p_b = new B(*(src.m_p_b)); //重新分配一块内存
        cout << "类A的拷贝赋值运算符执行了" << endl;
    }
    //移动赋值运算符
    A& operator=(A&& src) noexcept
    {
        if (this == &src)
            return *this;

        delete m_p_b; //释放自己的这块内存
        m_p_b = src.m_p_b; //指向对方指向的内存
        src.m_p_b = nullptr; //对方置空
        cout << "类A的移动赋值运算符执行了" << endl;
    }
public:
    B* m_p_b;
};


static A get_A()
{
    A a;
    return a; //生成临时对象 调用拷贝构造函数
}

int main()
{
    A a = get_A(); //1个构造函数,1个移动构造函数,1个析构函数
    A a_1;  //1个构造函数

    a_1 = std::move(a); //调用移动赋值运算符
    return 0;
}

合成的移动操作

某些条件下,编译器会合成移动构造函数,移动赋值运算符

  1. 有自己的拷贝构造函数,自己的拷贝赋值运算符,或者自己的析构,那么编译器就不会为它合成移动构造函数和移动赋值运算符
  2. 如果没有自己的移动构造函数和移动赋值运算符,系统会调用我们自己写的拷贝构造函数和拷贝赋值运算符来代替;
  3. 只有一个类没有定义自己的拷贝构造成员(拷贝构造函数和拷贝运算符),且类中的每个非静态成员都可以移动时,编译器才会为该类合成移动构造函数或者移动赋值运算符;

什么叫做成员可以移动?

  1. 内置类型可以移动;

  2. 类类型成员,如果这个类有对应的移动操作相关的函数,就可以移动;

    //举例
    struct TC
    {
        int i; //内置类型可以移动
        string a; //string类中定义了自己的移动成员
    }
    
    int main()
    {
        TC a;
        a.i = 100;
        a.s = "Hello world";
        const char *p = a.s.c_str();
    
        TC b = std::move(a); //导致TC类的合成移动构造函数(编译器生成的)执行;
        const char *q = b.s.c_str();
    
        //p和q的地址不一样,由于string对象的特性决定的;
        //这种移动,不是真正的移动,只是拷贝;
        return 0;
    }
    

总结

  • 尽量给类增加移动构造函数和移动赋值运算符;
  • noexcept;
  • 记得将被移动对象赋值给nullptr,让被移动对象随时处于一种能够被析构的状态;
  • 没有移动,会调用拷贝代替;

原文链接: https://www.cnblogs.com/NaughtyCoder/p/13359469.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍;

也有高质量的技术群,里面有嵌入式、搜广推等BAT大佬

    【C/C++】【类和对象】对象移动和移动构造函数

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/369594

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年3月2日 下午7:01
下一篇 2023年3月2日 下午7:01

相关推荐