zoukankan      html  css  js  c++  java
  • C++11核心知识点 —— 移动语义

    这篇根据一些文章整理,对移动语义进行详细记录

    移动语义

    const& 复制构造存在的问题

    复制构造在前面的文章中有记录,它的主要一个问题在于使用const &进行常引用,导致被复制的对象不能修改。按照常理来说,一般不需要修改被复制的对象,但在某些情况下却非常有用。
    首先看个代码:

    Person make_person(){
        auto person=Person();
        return person;
    }

    分析:
    首先产生一个局部对象,由于返回的是一个对象,因此产生复制构造操作,而且要求复制构造函数必须为const &的形式,否则出现错误:找不到合适的复制构造函数。因此Person的定义至少像这种形式:

    class Person{
    public:
        Person(const Person &person){...} //const &复制构造
    };

    这种样式是没有问题的,但是考虑一种情况,如果Person拥有资源怎么办?假如它有一个char *分配了100个字节的堆,那么问题演变成下面的模型:

    模型:已知A有资源R,现在希望能将R的所有权移给B,出发点在于避免重复多次的分配资源

    由于复制构造是常引用,我们可以做到把B.R = A.R,这样B拥有了A的资源,但是两者都指向同一块资源,在析构中势必会delete资源,那么B得到的资源将会无效!
    因此思路可以转换为:如果有一个复制构造不仅可以赋值,而且可以修改被复制的对象,那么这个问题就解决了。

    解决思路如下:B.R = A.R,然后将 A.R =nullptr。这样对象A即使析构使用delete也没有问题。在这种需求下,移动复制构造隆重出场!

    移动复制构造

    移动复制构造主要和右值有关系。右值主要包括临时对象和字面量的形式。在C++中有几种复制构造,因此编译器会选择最合适的重载。还是以上面的例子为例,在产生临时对象这一步,编译器选择的重载顺序如下:
    移动复制构造 > const &复制构造

     代码比较清楚的说明这一点,在临时对象采用哪种形式的构造上,编译器首选是移动复制构造,其中第四步的std::move下面记录下。

    class Person {
    public:
        char *m_pSource; //资源
        Person(const Person &person) {
            std::cout << "复制构造执行" << std::endl;
        }
        Person(Person &&person) {
            this->m_pSource = person.m_pSource;
            person.m_pSource = nullptr;
            std::cout << "移动复制构造执行" << std::endl;
        }
        Person() {
            m_pSource = new char[100];
            printf("原始资源:%p
    ", m_pSource);
        }
        ~Person() { 
            delete[]m_pSource; //释放资源
            std::cout << "析构执行" << std::endl; 
        }
    };
    Person test_Person() {
        Person person = Person();
        return person;
    }
    
    int main()  
    {
        Person p1 = test_Person();
        Person p2(std::move(p1));
        printf("p2资源:%p
    ", p2.m_pSource);
        printf("p1资源:%p
    ", p1.m_pSource);
    }

    再论右值及std::move

    std::move等价为对左值执行static_cast<T &&>操作,那么原对象将会变成临终值,临终值也是右值的一种形式。用处在于可以选择移动复制构造的重载。正如上面4标识的,std::move(p1)之后将会调用右值引用的重载。
    如果不使用,则调用const &的重载,比如 Person p3(p2)。

    经过一系列的move,原始资源从最初的局部对象person,最终辗转到p2,person -> p1 ->p2,printf说明了这一点

    一道测试题

    /*
        @一道测试题,如果能看懂代码为什么出问题,说明整个基础概念都了解了
        @tinaluo 2021-02-15夜
    */
    class Person {
    public:
        char *m_pSource; //资源
        Person(const Person &person) {
            std::cout << "复制构造执行" << std::endl;
        }
        Person(Person &&person) {
            this->m_pSource = person.m_pSource;
            person.m_pSource = nullptr;
            std::cout << "移动复制构造执行" << std::endl;
        }
        Person() {
            m_pSource = new char[100];
            printf("原始资源:%p
    ", m_pSource);
        }
        ~Person() { 
            delete[]m_pSource; //释放资源
            std::cout << "析构执行" << std::endl; 
        }
    };
    
    Person test_args(Person person) {
        printf("add %p
    ", person.m_pSource);
        return person;
    }
    int main()  
    {
        Person p1;
        printf("add %p
    ", p1.m_pSource);
        test_args(p1);
    }
  • 相关阅读:
    Scrapy 概览笔记
    Python 依赖版本控制 (requirements.txt 文件生成和使用)
    Python 虚拟空间的使用
    macOS 所有版本 JDK 安装指南 (with Homebrew)
    鉴权那些事
    Java 位运算符和 int 类型的实现
    ASP.NET Core 入门教程 5、ASP.NET Core MVC 视图传值入门
    如何做好一次知识或技术分享
    ASP.NET Core 入门教程 4、ASP.NET Core MVC控制器入门
    ASP.NET Core 入门教程 3、ASP.NET Core MVC路由入门
  • 原文地址:https://www.cnblogs.com/tinaluo/p/14403167.html
Copyright © 2011-2022 走看看