第十二章 深复制浅复制&静态成员函数&定位new

本文深入探讨了C++中的深拷贝和浅拷贝概念,强调了它们在处理包含指针或引用的类时的重要性。默认的复制构造函数和赋值运算符可能导致悬挂指针问题,因此需要自定义深拷贝以确保正确地复制数据。同时,介绍了静态成员函数的特性,以及定位new运算符在内存管理中的作用,包括与delete的配合使用和注意事项。文章还提醒开发者注意析构函数的显式调用,以避免内存泄漏问题。
摘要由CSDN通过智能技术生成

浅复制和深复制

假如类内声明了指针或引用等数据结构,那么在其进行构造新变量的时候就需要额外注意这些数据。

浅复制和深复制的诞生原因大部分是因为类地动态内存分配。

我们想让类成员在程序运行时决定需要多少分配内存,所以我们定义一个成员指针,在其构造函数是使用new运算符动态分配内存。

但请考虑下面的情况。

myString stringA(stringB);

stringA调用复制构造函数将stringB作为参数传递,如果你没有实现复制构造函数,那么编译器会自动生成一个默认复制构造函数。

默认构造函数仅仅只负责将值复制,也就是说它仅仅只是吧stringB中字符串的地址复制到stringA中。这意味着stringA和stringB中存储的字符串是同一个字符串,在程序结束时,stringA率先调用析构函数删除该字符串,这个时候stringB的字符串内容就指向一个已经被释放的地址,这会发生危险。

这个问题在函数pass by value时候极为常见。

以下是复制构造函数调用的四种情况

  • myString ditto(motto);

  • myString metoo=motto;

  • myString also=myString(motto);

  • myString* pms=new myString(motto);

    浅复制的另一情况是重载赋值运算符。

    同样,如果你没有认真处理重载的运算符,也会发生这样的错误。

    假如你没有定义重载赋值运算符

myString stringA;
stringA=stringB;

编译器同样会自动生成重载的赋值运算符,它和复制构造函数一样仅仅只是复制值。

在编写自己的赋值运算符需要记住几点。

  • 查看赋值的对象是否是自身,也就是

    if(this==&object) return *this;
    
  • 返回常对象参数的引用,这是为了实现连续赋值。

    typename& operator=(const typename& object);
    
  • 内部需要delete之前的旧数据

    总之,为了防止在创建新对象和赋值时候出现问题,对于哪些只存储值的对象,比如int,double等,可以直接赋值。但是对于指针引用等需要地址的数据,需要释放掉之前的数据,然后重新申请空间,将数据复制过去才行。这就是deep copy

静态成员函数

可以定义静态成员函数,但是静态函数成员不属于任何一个创造的变量,所以无法通过成员运算符调用。

要想正确调用静态成员函数,需要使用域解析运算符。

而且,因为静态成员函数不属于任何变量,所以它也无法访问任何的私有数据成员,只能处理静态成员变量。

定位new

假设我们频繁的使用new来申请空间,内存中很有可能存在简短的狭窄内存,每次申请空间都需要查找足够的内存空间分配,这回带来一些开销。事实上,c++提供一种定位new操作让内存在实现设定好的空间中申请。


new (address) typename(initialization);

由于address已经事先分配好了,所以定位new运算符只负责构建对象,即调用构造函数,而不负责申请空间。

通过定位new运算符,可以在栈上申请空间,也可以在堆上申请空间。

但是有几点需要注意。

  • delete能与new配用,但是不能和定位new配用。

  char* buffer= new char[512];
  A* testA=new (buffer) A;
  delete[] buffer;

首先,delete[] buffer不会调用A的析构函数,虽然事实上A的内存空间的确被释放掉了。也许你会想使用delete来删掉A的内存,但是仔细思考,new和delete系统动态的管理目前已经分配的空间,之前申请了512字节的空间被记录下来,之后定位new不申请空间,所以系统还是记录512字节已经被分配。但是由于delete和定位new不配用,如果这个时候调用delete A,那么系统就会减去之前记录的大小也就是512,清空了原有已经分配的内存大小,造成不可预估的后果。

所以delete A实质上是清空了buffer,虽然事实上必须使用delete[]才可以。

  • 但是不调用A的析构函数依旧是个问题,所以我们需要显式的调用testA的析构函数。

testA->~A();

这是少数需要显式调用析构函数的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值