[c++] 深拷贝和浅拷贝,拷贝构造、赋值运算符

本文详细探讨了C++中的拷贝构造、赋值运算符以及浅拷贝和深拷贝的概念,通过示例展示了它们在不同场景下的行为,包括函数调用、对象初始化和派生类的拷贝构造。
摘要由CSDN通过智能技术生成

1 拷贝构造和赋值运算符

1.1 拷贝构造

拷贝构造在如下场景会被调用:

(1)函数调用时,函数参数是对象的值传递

(2)声明对象同时初始化的时候(而不是声明和初始化分开,因为声明的时候就创建了对象)

(3)函数返回的时候,返回对象的值。

第 3 种情况,默认情况下有返回值优化,不会调用拷贝构造函数。

通过编译参数 -fno-elide-constructors 可以禁用返回值优化。

注:

拷贝构造函数的形参必须是引用传递,如果是值传递的话,那么在传递过程中也会调用拷贝构造函数,这样就造成递归调用。

如果是值传递,会有编译错误。

#include <iostream>
#include <string>

class Test {
public:
  Test(int x) {
    a_ = x;
    std::cout << "Test(), this = " << this << std::endl;
  };

  Test(Test &test) {
    std::cout << "Test() copy constructor, this = " << this << ", &test = " << &test << std::endl;
  }

  ~Test() {
    std::cout << "~Test(), this = " << this << std::endl;
  }

  int a_;
};

void func1(Test test) {
  std::cout << "func1, &test = " << &test << std::endl;
}

Test func2() {
  Test t(2);
  std::cout << "func2, &t = " << &t << std::endl;
  return t;
}

int main() {
  std::cout << "before t1" << std::endl;
  Test t1(1);
  std::cout << "&t1 = " << &t1 << std::endl;
  std::cout << std::endl;

  std::cout << "before func1" << std::endl;
  // 函数调用,传参是对象的值传递,拷贝构造
  func1(t1);
  std::cout << std::endl;

  std::cout << "before t2" << std::endl;
  // 声明对象的时候使用另一个对象初始化,拷贝构造
  Test t2 = t1;
  std::cout << "&t1 = " << &t1 << ", &t2 = " << &t2 << std::endl;
  std::cout << std::endl;

  std::cout << "before func2" << std::endl;
  // 函数返回值是返回对象的值,拷贝构造
  Test t4 = func2();
  std::cout << "&t4 = " << &t4 << std::endl;
  std::cout << std::endl;

  std::cout << "before return" << std::endl;
  return 0;
}

编译命令:

g++ copy1.cpp -fno-elide-constructors

运行结果:

1.2 赋值运算符

赋值运算符,也是通过 = 赋值的时候调用。前提是 = 左边的对象已经构建了。

赋值运算符返回的是当前对象的引用,因为赋值运算符并没有创建新的对象,所以 = 左边的对象仍然指向原来的位置。

#include <iostream>
#include <string>

class Test {
public:
  Test(int x) {
    a_ = x;
    std::cout << "Test(), this = " << this << std::endl;
  };

  Test(Test &test) {
    std::cout << "Test() copy constructor, this = " << this << ", &test = " << &test << std::endl;
  }

  Test& operator=(const Test &test) {
    std::cout << "Test() operator=, this = " << this << ", &test = " << &test << std::endl;
    return *this;
  }

  int a_;
};

void func1(Test test) {
  std::cout << "func1" << std::endl;
}

Test func2() {
  Test t(2);
  std::cout << "func2" << std::endl;
  return t;
}

int main() {
  std::cout << "before t1" << std::endl;
  Test t1(1);
  std::cout << std::endl;

  std::cout << "before t2" << std::endl;
  Test t2(10);
  std::cout << "before t2 = t1" << std::endl;
  // 赋值运算符
  t2 = t1;
  std::cout << "&t2 = " << &t2 << std::endl;
  std::cout << std::endl;

  std::cout << "before func2" << std::endl;
  Test t3(3);
  // 赋值运算符
  // 默认情况下开启了返回值优化
  // 在 func2 中构造的对象会直接通过赋值运算符赋值给 t3
  // 如果禁用返回值优化,那么下边这句代码调用了拷贝构造函数,也调用了赋值运算符
  t3 = func2();
  std::cout << "&t3 = " << &t3 << std::endl;
  std::cout << std::endl;

  std::cout << "before return" << std::endl;
  return 0;
}

1.3 拷贝构造和赋值运算符的区别

是否有新对象产生,有新对象产生则是拷贝构造,否则是赋值运算符。

Test t1(10);

Test t2(20);

t1 = t2;

赋值运算符,t1 已经存在了,已经构造过了,只不过重新赋值。

没有产生新对象,所以调用的是赋值运算符。

Test t1(10);

Test t2 = t1;

拷贝构造,t2 之前还没有存在,在执行 Test t2 = t1 的时候才构造出来一个新的。

产生了新的对象,所以调用了拷贝构造。

2 深拷贝和浅拷贝

浅拷贝和深拷贝在有指针成员的时候需要注意。

如果拷贝的时候只把指针进行了拷贝,两个对象内的指针指向同一个内存块,这就是浅拷贝,浅拷贝,两个对象会相互影响。如果拷贝的时候,重新申请了内存,然后把指针指向的数据进行了拷贝,那就是深度拷贝。

直观理解的话,实际开发中,绝大多数情况下都应该使用深拷贝,避免使用浅拷贝,因为浅拷贝使用不当很容易导致问题。

2.1 浅拷贝

如下代码, Test 类中有一个指针 int *pi_,可以认为是一个 int 数组。在构造函数中如果没有指定数组长度那就申请 8 个长度,如果指定了,那申请指定的长度。

类中没有自己写拷贝构造函数,也没有自己实现赋值运算符,当自己没有指定的时候,编译器会自动生成拷贝构造函数和赋值构运算符。

#include <iostream>
#include <string>

class Test {
public:
  Test(unsigned int length = 0) {
    std::cout << "Test(), this = " << this << ", pi_ = " << pi_ << std::endl;
    if (length == 0) {
      pi_ = (int *)malloc(8 * sizeof(int));
      length_ = 8;
    } else {
      pi_ = (int *)malloc(length * sizeof(int));
      length_ = length;
    }
    std::cout << "pi_ = " << pi_ << std::endl;
  };
/*
  Test(Test &test) {
    std::cout << "Test() copy constructor, this = " << this << ", &test = " << &test << std::endl;
    std::cout << "pi_ = " << pi_ << ", test length = " << test.length_ << std::endl;
    pi_ = (int *)malloc(test.length_ * sizeof(int));
    length_ = test.length_;
    std::cout << "after copy, pi_ = " << pi_ << ", test length = " << test.length_ << std::endl;
  }

  ~Test() {
    std::cout << "~Test(), this = " << this << ", pi_ = " << pi_ << ", length_ = " << length_ << std::endl;
    if (pi_) {
      free(pi_);
    }
  }
*/
  int *pi_ = nullptr;
  unsigned int length_ = 0;
};

void func1(Test test) {
  std::cout << "func1, &test = " << &test << std::endl;
}

Test func2() {
  Test t(2);
  std::cout << "func2, &t = " << &t << std::endl;
  return t;
}

int main() {
  std::cout << "before t1" << std::endl;
  Test t1(1);
  *t1.pi_ = 100;
  std::cout << "&t1 = " << &t1 << ", t1 data p = " << t1.pi_ << std::endl;
  std::cout << std::endl;

  Test t2 = t1;
  std::cout << "t2 data p = " << t2.pi_ << std::endl;

  Test t3(1);
  *t3.pi_ = 200;
  std::cout << "before assign, t3 data p = " << t3.pi_ << ", data = " << *t3.pi_ << ", t1 data = " << *t1.pi_ << std::endl;
  t3 = t1;
  std::cout << "after assign, t3 data p = " << t3.pi_ << ", data = " << *t3.pi_ << std::endl;

  std::cout << "before return" << std::endl;
  return 0;
}

代码编译之后,运行结果如下。

(1)拷贝构造

t2 是使用拷贝构造构造出来的对象,打印了 t2 中 pi_,可以看到 t2 中的 pi_ 和 t1 中的 pi_ 是相同的。也就是说默认情况下的拷贝构造是浅拷贝。

(2)赋值运算符

t3 被 t1 赋值前后,打印 t3 的 pi_,赋值前后, t3 中的 pi_ 是不相同的,赋值之后 t3 中的 pi_ 与 t1 中的 pi_ 是相同的。所以赋值运算符也是只把指针进行了赋值,没有对内存中的值进行赋值。

2.2 深拷贝

如下代码,自己实现了拷贝构造函数和赋值运算符,申请了空间,并把内存区域中的数据进行了复制。

#include <stdlib.h>
#include <string.h>
#include <iostream>
#include <string>

class Test {
public:
  Test(unsigned int length = 0) {
    std::cout << "Test(), this = " << this << ", pi_ = " << pi_ << std::endl;
    if (length == 0) {
      pi_ = (int *)malloc(8 * sizeof(int));
      length_ = 8;
    } else {
      pi_ = (int *)malloc(length * sizeof(int));
      length_ = length;
    }
    std::cout << "pi_ = " << pi_ << std::endl;
  };

  Test(Test &test) {
    std::cout << "Test() copy constructor, this = " << this << ", &test = " << &test << std::endl;
    std::cout << "pi_ = " << pi_ << ", test length = " << test.length_ << std::endl;
    pi_ = (int *)malloc(test.length_ * sizeof(int));
    length_ = test.length_;
    memcpy(pi_, test.pi_, test.length_ * sizeof(int));
    std::cout << "after copy, pi_ = " << pi_ << ", test length = " << test.length_ << std::endl;
  }


  Test& operator=(const Test &test) {
    std::cout << "Test() operator=, this = " << this << ", &test = " << &test << std::endl;
    if (pi_) {
      free(pi_);
    }
    pi_ = (int *)malloc(test.length_ * sizeof(int));
    if (pi_) {
      length_ = test.length_;
      memcpy(pi_, test.pi_, test.length_ * sizeof(int));
    }
    return *this;
  }

  ~Test() {
    std::cout << "~Test(), this = " << this << ", pi_ = " << pi_ << ", length_ = " << length_ << std::endl;
    if (pi_) {
      free(pi_);
    }
  }

  int *pi_ = nullptr;
  unsigned int length_ = 0;
};

void func1(Test test) {
  std::cout << "func1, &test = " << &test << std::endl;
}

Test func2() {
  Test t(2);
  std::cout << "func2, &t = " << &t << std::endl;
  return t;
}

int main() {
  std::cout << "before t1" << std::endl;
  Test t1(1);
  *t1.pi_ = 100;
  std::cout << "&t1 = " << &t1 << ", t1 data p = " << t1.pi_ << ", data = " << *t1.pi_ << std::endl;
  std::cout << std::endl;

  Test t2 = t1;
  std::cout << "t2 data p = " << t2.pi_ << ", data = " << *t2.pi_ << std::endl;
  std::cout << std::endl;

  Test t3(1);
  *t3.pi_ = 200;
  std::cout << "before assign, t3 data p = " << t3.pi_ << ", data = " << *t3.pi_ << ", t1 data = " << *t1.pi_ << std::endl;
  t3 = t1;
  std::cout << "after assign, t3 data p = " << t3.pi_ << ", data = " << *t3.pi_ << std::endl;

  std::cout << "before return" << std::endl;
  return 0;
}

t2 是调用拷贝构造函数,通过 t1 拷贝构造而来。可以看到拷贝构造之后,t2 中成员指向的内存和 t1 是不同的,内存中的值是相同的,所以是深度拷贝。

t3 被 t1 通过赋值运算符赋值,赋值之后,t3 指针指向的内存的值 与 t1 保持一致,所以是深度赋值。

2.3 派生类拷贝构造调用父类拷贝构造

#include <iostream>

class Base {
public:
  Base() : i(0) {
    std::cout << "Base()" << std::endl;
  }

  Base(int n) : i(n) {
    std::cout << "Base(int), i = " << i << std::endl;
  }

  Base(Base &b) : i(b.i) {
    std::cout << "Base(Base &), i = " << i << std::endl;
  }

private:
  int i;
};

class Derived : public Base {
public:
  Derived() : Base(0), j(0) {
    std::cout << "Derived()" << std::endl;
  }

  Derived(int m, int n) : Base(m), j(n) {
    std::cout << "Derived(int)" << std::endl;
  }

  Derived(Derived &d) : Base(d), j(d.j) {
    std::cout << "Derived(Derived &)" << std::endl;
  }

private:
  int j;
};

int main() {
  Base b(1);
  Derived d(2, 3);

  std::cout << "----------------" << std::endl;
  Derived d1(d);
  std::cout << "----------------" << std::endl;
  return 0;
}
  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值