原则27:要求或禁止在堆中产生对象

原则27_1:如何在堆中建立对象?
即,除了new以外用其他方法创建对象都不行。
其实这只需要把构造函数和析构函数中的至少一个设成private即可。如果构造函数是private而析构函数是非private的,那么你无法在外界正常常见对象。如果构造函数是public而析构函数是private的就会造成产生的对象无法被析构,这时C++判定此对象不合法。如果构造函数和析构函数都是private那就不必再说了。
所以书中给出的建议是你只需要private析构函数即可,然后你再写一个伪析构函数来调用真析构函数完成析构工作。
那为什么选择private析构函数呢?因为每个类的析构函数只有一个,这样方便,而类的构造函数有很多。
不过因为private析构函数的关系一个对象无法正常建立,这也导致类不能用于父类和复合。
到此,你可以通过类的实例的指针来使用类了。
此时,我突然想到指针相当于零存零取,而声明对象相当于整存争取。对象要求完整性,而指针则不。
原则27_2:如何解决不能继承和不能复合的问题?
继承从访问权限的角度讲是从protected和public为前提的,因此如果你想让类被继承,那它的构造函数和析构函数都不能是private的。但是你又想让类的对象只建立在堆上所以你只好选择把析构函数设成protected的。因为这样的话外界仍然不能如常地建立一个合法的对象。
而复合是说你把类的对象放到别的类中充当成员。因为原则27_2中谈论的就是通过new建立对象的方法,这说明类的对象的指针可以如常使用,那么你只需要让该类的指针充当其他类的成员即可。
原则27_3:采用异常+NEW来判断对象是否在堆上会失败
还是上面的例子,假如A是一个只能产生对对象的类,现在A的指针被作为B的成员,但是现在B没有被要求建立对对象。这就说明B能够正常建立对象。
那么问题来了。B对象中A的成分是建立在堆上?还是在栈上?
答案是不知道。
书中提出了几种解决方案。
解决方案一:因为如果对象在堆上建立,那肯定是需要调用new操作符,你可以自定义一个new调用真正的new,并设置一个标志位如果new操作符代码得以执行,该标志位就会被置位,显然标志位在自定义new操作符代码中。并且,该标志位在构造函数中还原。按照常理来说,你声明一个对象,就得new一次,调用构造函数一次。然后,你再写一个嵌套异常类,一旦检测到标志位为非堆,就抛出该异常。
但是这不适用于new[],即生产数组堆对象,因为new[]只执行了一次而构造函数需要执行n次。当你第2次执行构造函数的时候,你的程序会因为标志位为非堆而产生异常,这样就达不到判断的目的。
原则27_4:使用置位来判断对象是否在堆中
书中还提供了一种方法,就是用new来建立一个对象,那么这个对象肯定在堆上。然后用另一个new出来的对象给这个new对象初始化。这有点像拆了



东墙补西墙。
因为括号中的对象没有用delete删除所以它被留在堆中造成了内存泄露。作者的希望原本是new一次构造一次,在new的时候置位一次,构造的时候再置位一次,这样连续两次,每次都可以通过判断位来推断对象是否在堆中。但是实际上编译器可能的顺序是new、new、构造、构造,这样第二次new的置位被第三次构造清除,第四次构造的时候判断出该对象不在堆中,即便它实际上在堆中。所以,这也是一种失败的解决方案。
它太依赖于编译器的独特性,不具有共性,因此不具有可移植性。
原则27_5:用地址空间比较来判断对象是否在堆中
因为在很多系统中,栈在高地址空间,堆在低地址空间,这样的话你可以通过判断地址大小关系来推断对象是不是在堆中。但是这也行不通,因为在很多系统中静态存储区和堆挨着而且比堆的地址空间还低,所以你通过地址比较这种方法并不能准确地判断出对象在不在堆上,也许它还能在静态存储区上。
所以这种方法也不可取。
原则27_5:没有方法能判断对象在堆上
很遗憾的是根本没有任何方法能判断一个对象是否在堆上,更准确地说是没有一种通用的,可移植的办法能判断对象是否在堆上。它必须完全依赖于特定的系统。
不是所有堆中的对象都能使用delete删除的。比如说,你声明了一个类A的指针,显然这个指针在堆上。但是,类A包含了类B的对象成员,这时如果你想delete类A的对象就会不安全,因为类B对象不在堆上。
书中说你可以把研究的对象从堆上的对象转移到一段地址空间上来。即,你只要清空分配给对对象的地址空间即可,这是可行的。因为地址空间的起址是知道的,清空和判断是否安全的方法也是可以写出来的,所以但是处理堆上的空间,这很容易。
不过这种方法有3个限制因素。
1、自定义版本的operator new和operator delete极易与其他软件的operator new和operator delete产生不兼容,从而影响可移植性;
2、这样做并非必要,但仍需开销;
3、用来验证指针是否在堆中的方法无法奏效。因为基于多继承和虚拟继承的类对象的地址不唯一,所以不能保证给定的地址是否在new所分配的地址空间中。
书中设计了一种能够判断分配、释放和判断安全与否的类,因为这是一种能够被普遍使用的类,所以它被设计成抽象类。它把由new分配的地址统统装进了一个static的list中,delete的时候也从这个list中删除。
当你应用dynamic_cast对指针进行转换时返回的指针是这块内存的起始处,它只能被应用到具有至少一个virtual函数的对象的指针上。可以把dynamic_cast应用到上面类的判断函数实现上。
这个类一旦设计完成就可以被应用到许多类上,用来跟踪指针,具体方法就是继承这个类。
这方法不适合于内置类型,只适合自定义类型。
原则27_6:禁止对象被直接实例化情况下建立堆对象
因为堆对象的建立总是需要new操作符,那么在类内部把new操作符设置为private即可,你就不能直接使用new了。
因为new可重用所以把operator new设成static,另外,一般new的用法是单独使用,这也决定你应该使用static修饰。
与之对应,你应该把operator delete也设成static private。从语法上来讲你可以不把delete设成private的,但是,因为new和delete历来都是搭配使用的,new什么样delete就什么样,所以一旦你不给与delete和new一样的待遇,总是让人感觉有些不舒服。
原则27_7:禁止对象作为子类的父类而在堆中被建立
在这种情况下,子类会继承父类的private new和delete,所以子类也不能建立堆上的对象。
这时你只需要在子类中把new和delete重新声明为public即可,它就会把父类的new和delete重写。我还担心重写new和delete会和重写普通函数有区别,但实际上一样。
这样整个继承体系用的都是子类的new和delete,父类的new和delete不会被使用。那么父类的对象也会在堆上被建立,所以在这种情况下无法阻止父类对象在堆上被建立,必须想别的办法来对付这种情况。



如下图所示:


原则27_8:禁止被嵌入的对象在堆中建立
在这种情况下只需要管理嵌入对象,只要把嵌入对象的new和delete声明为private即可,被嵌入的类无需做任何处理。被嵌入的类的对象在堆上被建立时并不会导致嵌入类对象在堆上被建立。
因为嵌入类对象通常是直接在被嵌入类中创建出来的,它本身就不是在堆中,而是在栈里面被建立的(这是我的推测)。

多谢捧场

如果您觉得我的文章有价值,那么赏脸打赏一个,鄙人感激不尽。不过,不打赏看看也是好的,如果有不对的地方,还请您多多指正。


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容