深层次的理解 c++ 11 std::move和右值传参

记得以前看csdn上的文章,看到过一篇文章的作者写过这样一句话:

你以为你理解和你真正理解是有差别的,这中间的差别就是几个小时的痛苦.

所以现在遇到很多我自以为简单的知识点,就算自己以为理解了,也会尝试着去进行实践,并去想下这背后的深层次的原因。

是用来解决什么问题,问题是怎么来的?

第一部分:问题是怎么来的?

c++中有2种赋值方式,一种是直接赋值,另一种是拷贝构造赋值 .
int a = 5 ;//拷贝赋值
int a (5) ;//直接赋值

如果我们没有自己重写拷贝构造函数,那么系统会默认的帮我们创建一个拷贝构造函数,拷贝构造函数的过程就是把内存完整的拷贝给新的对象,这样如果我们的类中含有指针的话就很容易出问题.

class HashPtrMem {
public:
    HashPtrMem():d(new int(0)){}

    HashPtrMem(HashPtrMem& other){
        std::cout << "HashPtrMem copy function" << std::endl;
        d = new int (*other.d);
    }

    ~HashPtrMem() {
        std::cout << "~HashPtrMem dealloc" << std::endl;
        delete d;
    }

    int *d ;
};

我们在main函数中,这样调用:

int main(int argc, const char * argv[]) {
HashPtrM a ;
    HashPtrM b = a;
}

HashPtrM b = a;会调用默认的拷贝构造函数(叫做浅拷贝,如果自己重写了拷贝构造函数,那么就叫做深层拷贝)

指针在我们内存中保存的是什么?? 是地址,这个时候拷贝的时候,其实也会把地址完整的拷贝到新的对象中。

在main函数即将结束的时候,a和b都会析构,但是因为指针d的地址其实是同一个,所以最后会报错误 :

malloc: *** error for object 0x1005010d0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug

解决的方案之一就是:

我们采用深度拷贝的方式,自己重写拷贝构造函数:

    HashPtrMem(HashPtrMem& other){
        std::cout << "HashPtrMem copy function" << std::endl;
        d = new int (*other.d);
    }

什么时候调用拷贝构造函数呢?

1.一个对象需要通过另一个对象进行初始化,比如我们的方式:
HashPtrM b = a;

2.函数参数以传值的方式进行传入

void GetTempPtr(HashPtrM b){
}

想想这里,其实对基础的掌握还是要求挺高的.

老铁们,在我们拷贝构造函数的时候,实际上是把堆内存拷贝一份给新对象,然后在函数结束后,又释放掉新创建的内存块。

所以这是问题点,在拷贝构造函数的过程中,内存复制2份,析构也是double。

OK,现在我们这里有这样一个需求:

在一个函数内部,有一个临时变量,这给函数即将结束,我们需要把这个临时变量的内容赋值给类的同类型的变量.

这给时候我们如果使用拷贝构造函数的话,那么就是内存拷贝一份,然后临时变量析够.

第二部分,那么我们应该如何优化呢????

一个类可能有int类型,指针类型,类类型各种类型,除了完整的内存拷贝,我们还可以这样做:

int等基本类型,直接赋值,也只能这么做.
对于指针,我们直接采用下面代码的操作:

类似这样的操作 :

int *p = new int (5);
int *q = p;
p = NULL;

小知识:

The C++ language guarantees that delete p will do nothing if p is equal to NULL。

这样新对象的指针也指向了实体的数据,然后原指针变为了NULL,变为NULL后,析构的时候 delete 是不会触发任何事件的.

类中的类类型,其实可以递归这样的方式

这给就是我们传右值的核心思想,数据只有一份,从原有的值就这样”移动了”新值中.

注意:怎么对右值进行操作的行为,是由编程者自己决定的,也就是我们自己决定的.

说起来抽象,举个例子:

//
//  main.cpp
//  0304
//
//  Created by zhangkai on 2018/9/6.
//  Copyright © 2018年 zhangkai. All rights reserved.
//

#include <iostream>

class person {
public:

    person()
    {
        age = 0;
        name = new char [10]{'a','b','c'};
    }

    //拷贝构造函数
    person(const person& other)
    {
        printf("the address of other is:%p \n" , &other);
         std::cout << "person copy" << std::endl;
        this->age = other.age;


        size_t len = strlen(other.name);
        name = new char [len + 1];
        memcpy(name, other.name, len);
        name[len] = '\0';
    }

    //返回 person 主要是为了实现链式表达式
    person operator=(person& other){
        std::cout << "person operator == " << std::endl;
        this->age = other.age;


        size_t len = strlen(other.name);
        name = new char [len + 1];
        memcpy(name, other.name, len);
        name[len] = '\0';

        return *this;
    }

    //注意行为都是由我们自己来决定的.s
    //移动构造函数
    person(person&& other){
         std::cout << "person move" << std::endl;
        this->age = other.age;
        this->name  = other.name;

        other.name = NULL;
    }



    void copy(person other){
        printf("other address:%p \n" , &other);
    }

    ~person()
    {
        std::cout << "person deconstructor" << std::endl;
        if(name == NULL)
        {
            std::cout << "name is null" << std::endl;
        }
        delete[] name;
    }

    int age;
    char *name;
};


//class Family {
//public:
//    Family(person son): son_(son){
//
//    }
//private:
//    person son_;
//};


int main(int argc, const char * argv[]) {
    // insert code here...

    person son;//默认构造函数
    son.age = 10;

   //拷贝构造函数

    printf("the address of son is:%p \n" , &son);

    //中间会产生临时变量
    //类似:person temp = son;然后会把son的内存完整的拷贝到临时变量temp中
    //这个时候地址是不一样的
    person son1 (son); // 和 person son1 = son 等价!

    //中间也会产生临时变量,但是person(person&& other)程序会执行这里
    //临时变量的内容都是son的,中间没有涉及到内存拷贝
    person son2 (std::move(son));



    return 0;
}

std::move只是把类型变为右值,当然类自己必须提供对右值对支持,编译器是自己不会处理右值的.

默认的复制构造函数由于必须添加 const,所以其实其行为已经定死,就只能是拷贝构造函数.

注意:

    person(person&& other){
         std::cout << "person move" << std::endl;
        this->age = other.age;
        this->name  = other.name;

        other.name = NULL;
    }

这里other.name必须置为null,不然析够的时候会问题.

猜你喜欢

转载自blog.csdn.net/zhangkai19890929/article/details/82457537