浅析类的“深拷贝”

 

㈠ new的基本知识:

 

先说说new操作符:new有三种:newoperator newplacement newnew是通过调用operator new这个库函数来实现内存分配的。new申请内存失败的话,会抛出异常std::bad_alloc,且返回值不为0,当然也可以指定不抛异常的new

 

直接new一个对象,对象是否初始化,与定义一个同类对象相似:

new X;      //调用类X的默认构造函数

new X(x)    //调用类X的构造函数用x来初始化。

new int      //变量未被初始化

new int(5)   //变量初始化为5

new X[5]     //会调用类X的默认构造

 

可以在类型名后加上一对(),使用默认值初始化:

new int()     //变量初始化为0

new int[5]()  //数组的5个元素都初始化为0

new int[5](2) //错误,不允许按指定值进行初始化

 

new int[0];      //可以申请长度为0的数组 

delete (int*)0;  //删除0指针是安全的

 

 

㈡ 什么时候需要“深拷贝”函数:

 

对下面的类:

class Test {

  int *arr;

  size_t sz;

 

 public:

  Test(size_t sz_=0): arr(new int[sz_]()), sz(sz_) { }

  ~Test()  { delete [] arr; }

  // other functions

};

 

这个类成员有一个是指针变量,构造函数和析构函数都涉及对内存的申请和释放。当使用下面代码时,就会出问题。

 

Test a;

Test b(a)   //等价于 Test b=a; 调用与Test::Test(Test&)类似的拷贝构造函数

Test c;

c = a;      //调用与Test::Test& operator=(Test&)类似的赋值函数

 

上面的代码在定义对象b时,会调用拷贝构造函数Test::Test(Test&)由于Test类没有提供类似的拷贝构造函数,编译器就默认生成一个,将类的成员逐个进行简单的赋值(这就是“浅拷贝”)。于是 b.arr a.arr 都指向同一块内存,对b.arr的操作,会破坏a.arr所指向的数据,而当b结束生存期,被析构时,b.arr 所指向的内存(同时也是a.arr所指向的内存)被释放。当对a.arr进行操作时,由于a.arr所指向的内存可能已经被回收或者重新分配给其它对象,因而会有无法估计的错误发生,而当a结束生存期,又a.arr所指向的内存再次执delete操作,可能会造成程序崩溃,甚至更严重。(这与newdelete的具体实现相关,有的实现版本是将分配好的内存信息存放在一个表中,delete时根据查表结果再操作;有的实现则是简单的多申请一点空间,储存用户所申请的内存大小信息,delete时根据该信息再操作。前者安全但效率低,后者高效,但不够安全。)

 

对对象c的赋值,除了会有类似的结果,还存在对象c原来所占用的内存未被释放,造成内存泄漏。

 

要避免上述情况发生,最简单的方法,是将要调用的函数声明为私有成员。如:

 

private:

  Test& operator=(Test&) ;   //: Test& operator=(const Test&)

  Test(Test&)                //:  Test(const Test&) 

这两个函数的声明,参数类型用值和引用都可以,加不加const都可以。

 

 

㈢ 自定义“深拷贝”函数:

 

① 对 拷贝构造函数(Test(Test&)),其参数肯定是采用引用而不是值。因为如果传值的话, Test (Test tmp),在构造tmp时,需要调用拷贝构造函数,也就是说会调用自身,因而会限入死循环。参数一般采用常引用,一方面是为了防止引用的对象被修改,另一方面因为常引用可以引用临时变量(比如进行隐式转换时生成的临时变量),而引用则不行,举个简单的例子:

void ff(std::string& other);        // 不允许 ff("hi"); 方式调用

void gg(const std::string& other);  // 可以   gg("hi");

因为调用时会执行std::string tmp("hi")生成一个临时对象,然后才是对这个对象进行引用。

 

 

Test::Test(const Test& other):arr(new int[other.sz]), sz(other.sz)

{

    std::copy(other.arr, other.arr + sz, arr);

}

 

② 对赋值函数,则要特别注意两点:是否自赋值;在出现异常时,要保证原来的对象不被修改。另外,operator=最好返回引用,这样对x=y=z,可以避免额外的临时对象产生。

 

Test& Test::operator=(const Test &other)

{

  //先分配新内存,再复制,最后释放旧内存,保证异常安全   

  int *tmp = new int[other.sz]; 

  std::copy(other.arr, other.arr + other.sz, tmp);

  delete [] arr;

  arr = tmp;

  sz = other.sz;

  return *this;

}

 

另外一种保证异常安全的做法是:先用other构造一个临时对象tmp,再将tmp的成员和原来的交换。

 

Test& Test::operator=(const Test &other)

{

  Test tmp(other);

  swap (tmp);   //swap为自定义的交换成员函数,并且不抛出异常

  return *this;

}

 

也可以写做:

 

Test& Test::operator=(Test other)

{

  swap (other);   //swap为自定义的交换成员函数,并且不抛出异常

  return *this;

}

 

这样写不仅代码更简洁,而且性能可能更好,在传入一个临时对象(比如说某个函数的返回值),比前一种写法少了一次拷贝构造函数的调用和一次析构函数的调用。

 

swap函数的实现可以采用:

 

void Test::swap(Test& other) throw()

{

  std::swap(arr, other.arr);

  std::swap(sz, other.sz);

}

 

第二种方法,先构造一个临时对象再交换,看似比第一种方法麻烦不少,但更易于维护:资源都是在构造函数内分配,在析构函数内回收。

 

自定义一个swap函数不仅方便两个对象间的数据交换,而且可以快速的释放某个对象所占用的内存(新建一个临时对象再与其交换即可,对vector等容器推荐使用交换的方法来释放所占用的内存。)

 

上面的交换函数用到了标准库的swap函数,该函数是内联的。调用库函数而不是自己手写的代码,一方面使代码更简洁,另一方面可以给编译器更多的优化空间,在经过编译器优化后,甚至比手写的代码性能要高。(比如说开启C++ 0x后交换两个类对象,可以采用std::move。再比如说,intelCPU有一个xchg指令可以直接交换两个变量,编译器可以(当然,这只是可以)针对这个指令进行优化。)

 

 

㈣ 类中指针指向自定义类型时的“深拷贝”函数

 

如果类A中的指针是指向自定义类B的话,则深度拷贝函数可以直接调用类B对应的拷贝函数。

 

class Test2 {

  Test *test;

 

 public:

  Test2(): test(new Test) { }

  Test2(const Test2& other): test(new Test(*other.test)) { }

  Test2 &operator=(const Test2& other) { *test = *other.test; return *this; }

  ~Test2() { delete test; }

};

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在Python中,可以使用copy模块中的deepcopy方法来实现列表的拷贝拷贝会创建一个完全独立的列表副本,无论多少层嵌套,得到的新列表都是和原来无关的。可以通过引入copy模块,并使用copy.deepcopy()来进行拷贝操作。例如: import copy old = [1,[1,2,3],3] new = copy.deepcopy(old) 在上述代码中,old是原始列表,new是拷贝得到的新列表。无论对new进行任何修改,都不会影响到old的值。这种方法是最安全、最清爽、最有效的拷贝方法。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [浅析Python中list的复制及拷贝与浅拷贝](https://download.csdn.net/download/weixin_38643269/12867045)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Python中列表List的复制(直接复制、浅拷贝拷贝)](https://blog.csdn.net/weixin_49899130/article/details/129380610)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值