C++ 类学习总结(四) 类的交换与移动操作、右值

对象交换

  1. 概念

    swap 是 STL 的一部分,交换操作即,将两对象的值彼此赋予对方。交互两个对象,需要进行一次拷贝和两次赋值操作。

    namespace std
    {
        template<typename T>
        void swap(T& a,T& b)
        {
            T temp(a);//构造一个临时副本;
            a = b;//两次赋值
            b = a;   
        }
    }
    
  2. 自定义 swap

    对于包含动态内存的类来说,拷贝会产生不必要的内存,本质上我们只需要交换指针即可。可以对类定义一个自己版本的 swap 来重载 swap 的默认行为。

    class Example
    {
    public:
        Example(string s ):b(new string(s)) {}  ;//构造函数
        ~Example() ;//析构函数
        Example( Example & emp) :b( new string(*emp.b)) {};//拷贝构造函数
        Example& operator= (const Example& emp) ;//拷贝赋值运算符
        friend void swap(Example& t,Example& s);//自定义swap,定义为友元,为访问私有成员
    private:
        int a;
        string *b;
    };
    inline void swap(Example& t,Example& s)
    {
        using std::swap;//如果包含自定义的 swap 函数, 则优先使用自定义的 swap 函数; 否则, 系统使用标准库的 swap 函数
        swap(t.b,s.b);//交换指针,非底层 string
        swap(t.a,s.a);
    }
    
  3. copy and swap

    定义了 swap 的类通常用 swap 来定义赋值运算符,即将左侧运算对象与右侧运算对象的一个副本进行交换,核心是右侧运算对象以传值方式传递给赋值运算符。使用了拷贝和交换的赋值运算符是异常安全的,且能正确处理自赋值。

    Example& Example::operator= (const Example emp) //值传递参数
    {
        swap(*this,emp);//交换左侧对象和局部变量 emp,emp中指针指向了左侧对象的旧内存
        return *this;// emp 被销毁,释放了旧内存
    }
    

对象移动

  1. 概念

    ①.左值和右值

    左值表达式表示的是一个对象的身份,左值具有持久的状态,变量是左值;右值表达式表示的是对象的值,右值要么是字面常量,要么是过程中的临时对象。

    ②.右值引用

    右值引用就是必须绑定到右值的引用,用过 && 来获得右值引用。右值引用只能绑定到一个将要销毁的对象:所引用的对象将要被销毁、该对象没有其他用户。

    int a = 2;
    int &&r = a*10;// r绑定到计算结果 
    

    右值引用可以给临时变量续命,不再被销毁

    int getVal();
    int i = getVal();// 产生两个变量,左值 i,表达式结束后依然存在;getVal()返回的临时值是右值,表达式结束后被销毁
    int&& j = getVal();// j 是右值的引用,getVal()返回的临时值不再被销毁,生命周期通过右值引用得以延续
    

    ③.移动

    移动即数据资源的转移,从一个对象转移到另个对象,更改了资源的所有权。

    ④.move 函数

    move 函数定义在头文件 utility 中,是用来将一个右值引用绑定到一个左值的标准函数。move

    调用 move 隐含的承诺我们将不再使用移动后的源对象,除了销毁它或者赋给它一个新值。

    int a = 2;
    int &&r = std::move(a);// r 是绑定到 a 右值的引用
    
  2. 移动构造函数

    移动构造函数的第一个参数是该类型的一个右值引用,且为必须非 const,因为必须要确保移动资源后源对象是可以被析构的。

    class Example
    {
    public:
        Example(string s ):b(new string(s)) {}  ;//构造函数
        ~Example() ;//析构函数
        Example( Example & emp) :b( new string(*emp.b)) {};//拷贝构造函数
        Example& operator= (const Example& emp) ;//拷贝赋值运算符
        //移动拷贝构造函数
        Example( Example&& emp) noexcept //承诺不抛出任何异常
            :b( emp.b),a(emp.a) 
        {
             b = nullptr;//承诺可被析构   
        };
    private:
        int a;
        string *b;
    };
    
  3. 移动赋值运算符

    移动赋值运算符执行与析构函数和移动构造函数相同的工作,也必须确保正确处理自我赋值。

    class Example
    {
    public:
        Example(string s ):b(new string(s)) {}  ;//构造函数
        ~Example() ;//析构函数
        Example( Example & emp) :b( new string(*emp.b)) {};//拷贝构造函数
        Example& operator= (const Example& emp) ;//拷贝赋值运算符
        //移动赋值运算符
        Example&  operator= ( Example&& emp)  noexcept //承诺不抛出任何异常
        {
             if( *this!= emp)
             {
                 delete b;//释放旧资源
                 b = emp.b;
                 a = emp.a;
                 emp.b = nullptr;//承诺可被析构 
                 
             }
            return *this;
        };
    private:
        int a;
        string *b;
    };
    
  4. 合成的移动操作

    ①.如果类自定义了拷贝操作或者析构函数,则不会生成默认移动操作。

    ②.只有类没有自定义拷贝操作且成员都可移动时,才会生成默认移动操作。

    ③.如果类定义了移动操作,那默认的拷贝操作将会定义成删除的

  5. 移动操作应用

    ①.移动右值,拷贝左值

    若一个类既有拷贝构造函数,又有移动构造函数,编译器使用普通的函数匹配规则来确定使用哪个构造函数。

    Example GetObj();//返回一个 Example 类型对象
    Example exp1,exp2;
    exp1 = exp2;//exp2 是左值,使用拷贝赋值
    exp1 = GetObj();//GetObj() 返回一个右值,使用移动赋值
    

    ②.若没有移动构造函数,右值也可以被拷贝

    T&& 可用转换成 const T&,当没有移动构造函数时,可用匹配为拷贝构造函数。

    ③.拷贝并交互赋值运算符

    拷贝并交互运算符的参数非引用类型,此参数需要进行拷贝初始化,依赖于实参类型:左值使用拷贝构造、右值使用移动构造。因此,单一运算符实现了拷贝赋值和移动赋值两种功能。

    //拷贝并交互赋值运算符
    Example& Example::operator= (const Example emp) //值传递参数
    {
        swap(*this,emp);//交换左侧对象和局部变量 emp,emp中指针指向了左侧对象的旧内存
        return *this;// emp 被销毁,释放了旧内存
    }
    //移动构造函数
    Example:: Example( Example&& emp) noexcept //承诺不抛出任何异常
    :b( emp.b),a(emp.a) 
    {
             b = nullptr;//承诺可被析构   
    }
    
  6. 引用限定符

    类似于 const 限定符的使用,可以使用 & 或者 && 来限定成员函数可以作用于的对象类型,必须在函数声明和定义中都加限定符。

    class A {
    public:
        A& operator=(const A&) &;
    };
    

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值