5.深拷贝与浅拷贝+返回值优化

深拷贝与浅拷贝+返回值优化

概念

在C++中,深拷贝(deep copy)和浅拷贝(shallow copy)是两种不同的对象拷贝方式,它们在处理动态分配内存时表现出不同的行为。

  1. 浅拷贝(Shallow Copy)

    • 浅拷贝是指将一个对象的数据成员的值复制到另一个对象,但如果对象中包含指向动态分配内存的指针,它们将指向同一块内存空间。

    • 这样,当一个对象被销毁时,它指向的内存空间会被释放。如果另一个对象仍然引用这片内存,那么这个内存将变成悬空指针,访问它可能导致未定义行为。

    • 使用默认的拷贝构造函数和赋值运算符进行拷贝时,通常会发生浅拷贝。

  2. 深拷贝(Deep Copy)

    • 深拷贝是指创建一个新的对象,然后将原对象中的数据成员复制到新对象中,包括指向动态分配内存的指针,而不是简单地复制指针本身。

    • 这样,原对象和新对象各自拥有一份独立的内存空间,对其中一个对象的操作不会影响另一个对象。

    • 为了实现深拷贝,通常需要自定义拷贝构造函数和赋值运算符,确保所有资源都被正确复制。

    这样说大家可能看不懂,这样咱在代码中进行理解

浅拷贝

 /*************************************************************************
         > File Name: test.cpp
         > Author:Xiao Yuheng
         > Mail:3312638794@qq.com
         > Created Time: Sat Oct 28 17:03:20 2023
  ************************************************************************/
 ​
 #include <iostream>
 ​
 using namespace std;
 ​
 #define BEGINS(x) namespace x {
 #define ENDS(x) }
 ​
 BEGINS(xyh)
 ​
 class A{
 public:
     int x,y;
     A(int x = 100, int y = 100) : x(x), y(y) {}
 ​
     ~A() {}
 };
 ​
 ENDS(xyh)
 ​
 int main() {
     xyh::A a;
     xyh::A b = a;
     cout << a.x << " " << a.y << endl;
     cout << b.x << " " << b.y << endl;
     return 0;
 }

我们先来看看这段代码的结果

由构造函数的知识可以知道xyh::A b = a;这行代码会调用拷贝构造函数,由于我们没有写,这时会调用系统自默认的拷贝构造,也就是说把a对象的值全部拷贝给b对象,所以没有问题,好我们接下来在来看一段代码:

 /*************************************************************************
         > File Name: test.cpp
         > Author:Xiao Yuheng
         > Mail:3312638794@qq.com
         > Created Time: Sat Oct 28 17:03:20 2023
  ************************************************************************/
 ​
 #include <iostream>
 ​
 using namespace std;
 ​
 #define BEGINS(x) namespace x {
 #define ENDS(x) }
 ​
 BEGINS(xyh)
 ​
 class A{
 public:
     int n;
     int *data;
     A(int n = 100) : n(n), data(new int[n]) {}
 ​
     ~A() {
         delete[] data;
     }
 };
 ​
 ENDS(xyh)
 ​
 int main() {
     xyh::A a;
     xyh::A b = a;
     return 0;
 }

我们直接看运行结果:

我们可以看见报错了,可是为什么呢,我们由错误可以知道我们free()了两次,可是为什么会这样呢?这我们就得提到浅拷贝的缺陷了,在第二个样例中我们xyh::A b = a;可以知道他会调用系统自带的默认拷贝构造函数,这时呢他会将a对象的值完全复制给b对象,包括指针指向的地址也是一样的,可当我们结束这段程序是,系统会调用析构函数来释放这段空间,可是由于a对象和b对象指向同一块地址,所以就导致我们释放了同一块内存两次,所以导致报错。我们可以在代码中加上这么一段代码:

 
int main() {
     xyh::A a;
     xyh::A b = a;
     cout << a.data << endl;
     cout << b.data << endl;
     return 0;
 }

把a.data和b.data这两个指针指向的地址打印出来,我们看运行结果:

我们可以发现地址是一样的。现在我们知道了,为什么报错了,可该怎么改呢?我们接着看深拷贝。

深拷贝

我们在之前的代码上手写一个拷贝构造

 /*************************************************************************
         > File Name: test.cpp
         > Author:Xiao Yuheng
         > Mail:3312638794@qq.com
         > Created Time: Sat Oct 28 17:03:20 2023
  ************************************************************************/
 ​
 #include <iostream>
 ​
 using namespace std;
 ​
 #define BEGINS(x) namespace x {
 #define ENDS(x) }
 ​
 BEGINS(xyh)
 ​
 class A{
 public:
     int n;
     int *data;
     A(int n = 100) : n(n), data(new int[n]) {}
     A(const A & a) : n(a.n), data(new int[n]){
         for (int i = 0; i < n; i++) {
             data[i] = a.data[i];
         }
     }
     ~A() {
         delete[] data;
     }
 };
 ​
 ENDS(xyh)
 ​
 int main() {
     xyh::A a;
     xyh::A b = a;
     cout << a.data << endl;
     cout << b.data << endl;
     return 0;
 }

我们在重新开辟一块空间,然后用b对象的data指向这片空间,然后我们在进行赋值操作,这样我们就不会出现错误信息了,我们来运行一下:

我们可以发现这a对象的data指针指向的地址和b对象的data指针指向的地址不一样了,好,在这样样例中我们深拷贝完成了,可是有没有一种很好的方法,可以适应各种情况呢?好我们接着往下看。

 /*************************************************************************
         > File Name: test1.cpp
         > Author:Xiao Yuheng
         > Mail:3312638794@qq.com
         > Created Time: Fri Oct 27 16:16:22 2023
  ************************************************************************/
 #include <iostream>
 ​
 using namespace std;
 ​
 template<typename T>
 class Vector {
 public:
     Vector(int n = 100) : n(n), data(new T[n]) {}
     Vector(const Vector &a) : n(a.n), data(new T[n]) {
         for (int i = 0; i < n; i++) {
             data[i] = a.data[i];
         }
         return ;
     }
     ~Vector() {
         delete[] data;
     }
 ​
     int n;
     T *data;
 ​
 };
 ​
 int main() {
     Vector<Vector<int>> arr1;
     Vector<Vector<int>> arr2(arr1);
     cout << arr1.data << endl;
     cout << arr2.data << endl;
     return 0;
 }

大家请看这段代码,还是相同的拷贝构造函数还能否继续执行正确呢?我们运行一下看看:

可以看见这里报错了,可是原因是什么呢?我们可以看见data[i]是一个数组指针,这时直接运行data[i] = a.data[i];是不是又相当于一次浅拷贝呢?这时我们应该怎么解决这个问题呢?

我们在这里引入一个新的概念,原地拷贝,什么是原地拷贝呢?大家可以这样理解:就是可以递归的进行拷贝构造。具体代码如下:

 /*************************************************************************
         > File Name: test1.cpp
         > Author:Xiao Yuheng
         > Mail:3312638794@qq.com
         > Created Time: Fri Oct 27 16:16:22 2023
  ************************************************************************/
 #include <iostream>
 ​
 using namespace std;
 ​
 template<typename T>
 class Vector {
 public:
     Vector(int n = 100) : n(n), data(new T[n]) {}
     Vector(const Vector &a) : n(a.n), data(new T[n]) {
         for (int i = 0; i < n; i++) {
             new(data + i) T(a.data[i]);
         }
         return ;
     }
     ~Vector() {
         delete[] data;
     }
 ​
     int n;
     T *data;
 ​
 };
 ​
 ​
 ​
 int main() {
     Vector<Vector<int>> arr1;
     Vector<Vector<int>> arr2(arr1);
     cout << arr1.data << endl;
     cout << arr2.data << endl;
     return 0;
 }

这样就可以很好的解决这个问题。

返回值优化

返回值优化(Return Value Optimization,简称RVO)是一种C++编译器的优化技术,用于避免在函数返回一个对象时发生不必要的拷贝操作。

在C++中,当一个函数返回一个对象时,通常会创建一个临时对象,然后将函数中的局部对象拷贝到这个临时对象中,最后返回这个临时对象的副本。这个过程涉及到了拷贝构造函数的调用。

RVO的实现原理是,编译器会在返回对象时直接在函数调用的地方构造该对象,而不是在函数内部构造一个临时对象然后再进行拷贝操作。这样可以节省内存和运行时开销。

这样说可能不太明确,我来通过一段代码告诉大家:

 /*************************************************************************
         > File Name: test3.cpp
         > Author:Xiao Yuheng
         > Mail:3312638794@qq.com
         > Created Time: Sat Oct 28 20:16:17 2023
  ************************************************************************/
 ​
 #include <iostream>
 ​
 using namespace std;
 ​
 class A{
 public:
     int x, y;
     A() {
         cout << "构造函数" << endl;
     }
     A(const A &a) {
         cout << "拷贝构造" << endl;
     }
 ​
 };
 ​
 A fun() {
     A temp;
     return temp;
 }
 ​
 int main() {
     A b = fun();
     return 0;
 }

大家猜一下这段代码会输出什么?我们来运行一下:

我们可以看见最后就输出了一个“构造函数”,可是按照原理来说我们应该先输出的是

  • 构造函数:A temp;这个会调用构造函数

  • 拷贝构造:return temp; 在拷贝给一个临时变量

  • 拷贝构造:A b = fun();最后临时变量在拷贝给b对象

这是因为我们现在的编译器基本都带有返回值优化,这里我们把返回值优化关了大家看一下:

这时我们可以看见他输出了我们上面输出的那三个运行结果;

这是我们原本应该经过的的过程,可当开启返回值优化之后会发生什么呢?

 /*************************************************************************
         > File Name: test3.cpp
         > Author:Xiao Yuheng
         > Mail:3312638794@qq.com
         > Created Time: Sat Oct 28 20:16:17 2023
  ************************************************************************/
 ​
 #include <iostream>
 ​
 using namespace std;
 ​
 class A{
 public:
     int x, y;
     A() {
         cout << "构造函数" << endl;
     }
     A(const A &a) {
         cout << "拷贝构造" << endl;
     }
 ​
 };
 ​
 void fun(A &temp) {
     return ;
 }
 ​
 int main() {
     A b;
     fun(b);
     return 0;
 }

我们看看这段代码,是的,开启返回值优化之后,差不多和这段代码一样,我们来看一下运行结果

是不是和开启返回值优化差不多呢!我们来分析一下这个代码,这时先定义一个对象b,然后在把b对象的引用传递到函数fun();然后在运行这个fun函数里面的内容。而返回值优化差不多也是同理。

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值