zoukankan      html  css  js  c++  java
  • “复制赋值”和“移动赋值”的思考

    概述

    从 C++ 11 中开始,该语言支持两种类型的分配:复制赋值移动赋值。其中的内部细节是咋样的呢?今天跟踪了一下,是个蛮有趣的过程。下面我们以一个简单的类来做个分析。

    #ifndef HASPTR_H
    #define HASPTR_H
    
    
    #include <string>
    
    class HasPtr {
    public:
        friend void swap(HasPtr&, HasPtr&);
        HasPtr(const std::string& s = std::string());
        HasPtr(const HasPtr& hp);
        HasPtr(HasPtr&& p) noexcept;
        HasPtr& operator=(HasPtr rhs);
        // HasPtr& operator=(const HasPtr &rhs);
        // HasPtr& operator=(HasPtr &&rhs) noexcept;
        ~HasPtr();
    
    private:
        std::string* ps;
        int i;
    };
    
    #endif // HASPTR_H
    
    
    #include "hasptr.h"
    #include <iostream>
    
    inline void swap(HasPtr& lhs, HasPtr& rhs)
    {
        using std::swap;
        swap(lhs.ps, rhs.ps);
        swap(lhs.i, rhs.i);
        std::cout << "call swap" << std::endl;
    }
    
    HasPtr::HasPtr(const std::string& s) : ps(new std::string(s)), i()
    {
        std::cout << "call constructor" << std::endl;
    }
    
    //这里的i+1只是为了方便调试的时候看过程,实际是不用加1的
    HasPtr::HasPtr(const HasPtr& hp) : ps(new std::string(*hp.ps)), i(hp.i + 1)
    {
        std::cout << "call copy constructor" << std::endl;
    }
    
    HasPtr::HasPtr(HasPtr&& p) noexcept : ps(p.ps), i(p.i)
    {
        p.ps = 0;
        std::cout << "call move constructor" << std::endl;
    }
    
    HasPtr& HasPtr::operator=(HasPtr rhs)
    {
        swap(*this, rhs);
        return *this;
    }
    
    HasPtr::~HasPtr()
    {
        std::cout << "call destructor" << std::endl;
        delete ps;
    }

    主函数

    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        HasPtr hp1("hello"), hp2("World"), *pH = new HasPtr("World");
        hp1 = hp2;
        hp1 = std::move(*pH);
    
        return a.exec();
    }

    下面我们开始调试:

    输出:

    我们通过构造函数构造了三个变量,他们的值和

      address ps i
    hp1 0x28fe64 "hello" 0
    hp2 0x28fe5c "World" 0
    pH 0x28fe9c "World" 0

     

    复制赋值

    我们接着单步走:

    可以发现首先调用了复制构造函数,构造了一个和hp2一样的临时变量

      address ps i
    this 0x28fe2c "World" 1
    hp2 0x28fe5c "World" 0

    下一步:

    到这里才开始进行赋值运算,我们对比一下数据:

      address ps i
    this 0x28fe2c "hello" 0
    rhs 0x28fe8c "World" 1

    这里的rhs就是我们刚刚分配的临时变量,那么this就是hp1,所以最终是我们的临时变量和hp1交换,我们接着走:

    这里lhs的地址就是:0x28fe64,就是hp1的地址,交换之后:

    到此hp1和临时变量的值就完全交换过来了,也就是说hp1 = hp1了。

      address ps i
    lhs 0x28fe64 "World" 1
    rhs 0x28fe8c "hello" 0

    可是我们接着运行,发现进入了一个析构函数:

    看一下地址是0x28fe8c以及其值,这是临时变量,临时变量不用了,所以被销毁了,至此我们的复制赋值运算就结束了。

    移动赋值

    我们看一下移动赋值赋值:

     

    首先进入移动函数,这里只是使指针指向了pH的数据,并未构造新的数据,变量右值引用了pH,只是相当于换了个名字。

    接下来开始进入赋值运算:

     

    这里两个交换的值是hp1和pH,和复制赋值不同,它是和临时变量交换数据,

     

    后面进入析构函数:

    它释放掉了pH的数据。

     

    可以看出来,pH的值被释放掉了。

    总结

    调试过后,我们发现,赋值运算的过程并非像想象中那么简单,是不是?复制赋值还是开辟一个临时变量用于转化,这个耗费了额外的空间资源。

    移动赋值就可以避免这个问题,但是需要注意的是,移动赋值使用的是右值,用完之后就被销毁了,所以,如果想把一个左值当做右值来用,必须确保这个左值在这之后不需要使用了。

     参考:

    1.   《C++ primer》
  • 相关阅读:
    springboo 添加logback日志
    logback配置日志输出
    认知升级:提升理解层次的NLP思维框架
    2019第29周日
    《如何有效社交》晨读笔记
    控制论模型&心流模型&波模型
    数学中常见的思维模型
    分布式服务跟踪系统
    Spring Cloud Sleuth
    微服务调用跟踪
  • 原文地址:https://www.cnblogs.com/vczf/p/6848446.html
Copyright © 2011-2022 走看看