【C++】深入理解浅拷贝问题

文章目录

  • 一. 浅层浅拷贝问题
  • 二. 深层浅拷贝问题
  • 三. 类型萃取

一. 浅层浅拷贝问题

问题分析

我们在学C语言的结构体的时候知道,可以用一个结构体对象去拷贝出另外一个结构体对象,其原理是通过memcpy来实现的,当时我们看到的结果确实是拷贝出了另一个值相同的结构体对象。
【C++】深入理解浅拷贝问题_第1张图片
我们来看看memcpy的拷贝原理,其实就是逐个字节的来完成拷贝。

void* memcpy(void* dest, const void* src, size_t num)//传入数据可能是各种类型的,所以用void*接收
{
    //断言,判断指针的有效性,防止野指针
    assert(dest!=NULL);
    assert(src!=NULL);
	//void*
	//可以接收所有类型的指针
	//不可以进行解引用和加减的操作,但可以比较大小
	void* tmp = dest;
	while (num--)
	{
		//把指针类型转化为char*在解引用和+1/-1时可以访问一个字节
		*(char*)dest = *(char*)src;
		((char*)dest)++;
		((char*)src)++;
	}
	return tmp;
}

由于当时我们还没有学类和对象的相关知识(其实结构体对象之间的拷贝哪里就存在浅拷贝问题),现在学习后知道了类其实和我们C语言学习的结构体差不多,类对象可以自己定义它的构造方式和生命周期结束后自动调用析构函数来完成清理工作。比如这里我们可以自己实现一个粗糙的string类,也来试试能不能像结构体一样完成拷贝对象的功能。
【C++】深入理解浅拷贝问题_第2张图片
通过监视窗口我们发现确实构造出来的s2和s1的值一样都是字符串"hello world!",由于我们没有实现MyString的拷贝构造所以这里的拷贝和上面结构体的一样是依靠memcpy来完成的。但是!!!我们运行到最后程序却崩溃了,这是因为用memcpy来拷贝MyString类对象后,s1和s2的成员变量_str都指向了同一块空间,最后析构时这块空间被析构两次而导致的程序崩溃。这就是所谓的浅层浅拷贝问题,他们通常发生在直接进行拷贝构造和赋值时
【C++】深入理解浅拷贝问题_第3张图片

解决办法

我们自己实现一个拷贝构造成员函数,在里面实现深拷贝:

  1. 开辟和拷贝对象一样大的空间
  2. 拷贝它的数据到新开辟的空间里
    【C++】深入理解浅拷贝问题_第4张图片

再次理解浅层浅拷贝

还是上面的MyString类,我们用memcpy去拷贝MyString类对象导致他们的_str指向同一块空间,最后这块空间两次释放导致程序崩溃。但是如果我们是去拷贝char*对象的话他们是不会指向对方的空间的,两个对象分别有不同的空间和相同的值,所以真正理解浅层浅拷贝的原因在于正确理解memcpy。
【C++】深入理解浅拷贝问题_第5张图片
这里在贴一次memcpy的实现原理

void* memcpy(void* dest, const void* src, size_t num)//传入数据可能是各种类型的,所以用void*接收
{
    //断言,判断指针的有效性,防止野指针
    assert(dest!=NULL);
    assert(src!=NULL);
	//void*
	//可以接收所有类型的指针
	//不可以进行解引用和加减的操作,但可以比较大小
	void* tmp = dest;
	while (num--)
	{
		//把指针类型转化为char*在解引用和+1/-1时可以访问一个字节
		*(char*)dest = *(char*)src;
		((char*)dest)++;
		((char*)src)++;
	}
	return tmp;
}

浅层浅拷贝总结

  1. 类对象的拷贝构造和赋值重载编译器默认用memcpy完成的,如果类的成员变量里有指向空间的指针变量的话,要考虑浅拷贝的问题。一般深拷贝的话就是让这些指针变量指向一块新开辟的空间,然后把对应的值拷贝到里面。
  2. 通常发生在直接进行拷贝构造和赋值时。

二. 深层浅拷贝问题

问题分析

这里我们来粗略实现vector的拷贝构造,和string的差不多。要注意的是vector不像string只能存储字符串,他可以存各个类型的数据,所以是个类模板。
【C++】深入理解浅拷贝问题_第6张图片
前面我们说MyString的拷贝构造里用memcpy拷贝char*对象是没问题的,确实完成了深拷贝,但是用他拷贝成员变量有指向一块空间的指针变量的类时就会有浅拷贝的问题。如果MyVector存储的是char、int等内置类型还好,如果是MyString的话就又出现前面的浅拷贝了,这既是深层浅拷贝,通常发生在泛型编程中局部进行数据的拷贝和赋值中。
【C++】深入理解浅拷贝问题_第7张图片

解决办法

如果元素是像MyString那样的自定义类型,就一定有自己的拷贝构造和赋值重载,且它们的拷贝构造和赋值重载内部一定是实现深拷贝的,既然它们类的内部解决了浅层浅拷贝问题,那么我们可以顺水推舟来解决目前面临的深层浅拷贝问题:我们利用operator[ ]遍历数组取得里面的每个元素,对应下标的被拷贝对象的每个元素赋值给他,实现深拷贝。
【C++】深入理解浅拷贝问题_第8张图片

深层浅拷贝总结

  1. 要满足泛型编程,适应不同数据类型,就不能用memcpy来拷贝数据,不然会出现深层浅拷贝,这时要利用那些自定义类内部完成的赋值重载的深拷贝,把这些自定义类型的元素单个取出来赋值完成单个的深拷贝。
  2. 通常发生在泛型编程中局部进行数据的拷贝和赋值中。

三. 类型萃取

对于深层浅拷贝,如果用memcpy来拷贝数据,数据只能是内置类型(int、char等),而取出每一个元素单独赋值的方法适用于任何类型。对于内置类型用深拷贝的话就太慢了。我们都知道C++是一个极度追求效率的语言,设计者可不甘心就这样对所有类型都一视同仁,所以发明了类型萃取。不难理解:类型萃取帮助我们提取出自定义类型进行深拷贝,而内置类型统一进行浅拷贝,也就是所谓的值拷贝。

你可能感兴趣的:(C++,c++)