13.2.3 指针的Stash
Stash的新版本称为PStash,它含有在堆中本来就存在的对象的指针。而前面章节中旧的Stash则是通过传值方式拷贝对象到Stash的容器。使用new和delete,控制指向在堆中创建的对象的指针就变得安全、容易了。
下面提供了“pointer Stash”的头文件:
基本的数据成分是非常相似的,但现在storage是一个void指针数组,并且用new代替malloc()为这个数组分配内存。在下面这个表达式中:
对象的类型是void*,所以这个表达式表示分配了一个void指针的数组。
析构函数删除void指针本身,而不是试图删除它们所指向的内容(正如前面所指出的,释放它们的内存但不调用析构函数,这是因为一个void指针没有类型信息)。
其他方面的变化是用operator[]代替了函数fetch(),这在语句构成上显得更有意义。因为返回一个void*指针,所以用户必须记住在容器内存储的是什么类型,在取回它们时要对这些指针进行类型转换(这是在以后章节中将要修改的问题)。
下面是成员函数的定义:
除了用储存指针代替整个对象的拷贝外,函数add()的效果和以前是一样的。
inflate()的代码被修改为能处理void*指针数组的存储,而不是先前的设计,只处理元比特。这里没有优先使用数组索引的拷贝方法,而是使用标准C库函数中的memset()来使所有新的内存置0(并不是一定要如此,因为PStash有可能正确地管理所有的内存,但小心点是没有害处的),然后用memcpy()把存在的数据从原来的地方移到一个新的地方。通常类似于memset()和memcpy()的函数随着时间会逐渐优化,所以它们会比前面所示的循环更快。但由于类似inflate()的函数可能没有被使用,所以一般看不出性能上的差异。然而这种比循环更简练的函数调用有助于防止编码错误。
为了由客户程序完全负责对象的清除,有两种方法可以获得PStash中的指针:其一是使用operator[],它简单地返回作为一个容器成员的指针。第二种方法是使用成员函数remove(),它返回指针,并且通过置0的方法从容器中删除该指针。当PStash的析构函数被调用时,它进行检查以确信所有的对象指针已被删除。如果注意到指针还没有被删除,则可以通过删除它来防止内存丢失(后面的章节中有更加智能的方法)。
13.2.3.1 一个测试程序
为了测试PStash,我们重写了Stash的测试程序:
与前面一样,我们创建了Stash对象,并且为它们加入了内容。不同的是这次的内容是由new表达式产生的指针。首先请注意这一行:
这个表达式new int(i)使用了伪构造函数形式,因此将在堆上创建了一块区域用来存储这个新的int对象,同时这个int对象被初始化为i。
打印时,由PStash:operator[]返回的值必须被转换为正确的类型,对于这个程序其余的PStash对象,也将重复这个动作。这是使用void指针的缺点,将在后面的章节中解决。
测试的第2步是打开源程序文件,并逐行把它读到每一个PStash里。首先用getline()把每一行读入一个String对象,然后对line进行new string操作,将这一行的内容拷贝下来。如果每次只是传送line的地址,将会得到指向line的一些指针,而此时line仅包含了所读文件的最后一行的内容。
当取回指针时,我们可以看到表达式:
为了使operator[]返回的指针具有正确的类型,它们必须被转换为String。然后,String被间接引用,所以此表达式的计算结果相当于一个对象,这时编译器也认为一个string对象被发送给了cout。
在堆上创建的对象必须通过remove()语句进行注销,否则将会实时地得到一个信息,告诉我们并没有完全清除那些在PStash中的对象。注意,对于int指针,类型转换不是必需的,因为int类的对象没有析构函数,我们所需要的仅是释放内存。
但是,对于string指针,如果忘记了类型转换,则会出现内存泄漏的情况。所以说进行类型转换是十分重要的。
这些问题的一部分(但不是全部)可以使用模板进行解决。(我们将在第16章中学习模板)。