写时复制(copy-on-write):在对资源进行写入之前,资源(在该例中就是字符)是共享的。当某共享资源的对象试图在资源中写入时,就制作一个副本。
注意:
一定要完全初始化对象。所有构造函数都应确保用合适的值初始化所有数据成员。
一定要为所有的类都实现复制构造函数、赋值操作符和析构函数。由编译器生成的默认版本在实际的商业级程序中几乎没用。
充分理解无用单元收集和悬挂引用的概念,确保设计的类不会发生内存泄漏。
正确理解对象的标识,不要混淆指向对象的指针和真正的对象。
为类提供复制和赋值(如果有意义的话)。在类不允许复制和赋值语义的地方,关闭(或控制)复制和赋值。
如果设计的实现将用于多线程系统中,应确保引用计数是多线程安全的。
为了让实现更加高效,使用“写时复制”的方案。
用复制构造函数操作代替使用默认构造函数后立即使用赋值的操作。
相等性判断显然,赋值和复制是类的设计者和实现者必须考虑的重要问题。另一个相关问题也同等重要,即对象相等(object equality)的概念。
首先要区别一下相等和等价的概念。对象相等要比较对象的结构和状态,而对象等价(object equivalence)则要比较对象的地址。两个不同的对象可能相等,但是不允许它们是同一个对象。
在上图中,person2和person3是相等对象,而person3和person4是等价对象。
在处理对象时,不同的语言定义对象相等的方式不同。例如,C++对于对象等价并未定义任何默认的含义,而Eiffel和Smalltalk则定义了默认含义。再者,不同程序员对对象间相等的解释也不同。接下来,先介绍基于引用的语言如何表示对象等价和对象相等。
SmalltalkSmalltalk为等价判断提供了方法,所有对象都可以使用该方法。如果方法返回值为 true,则待比较的两个对象是相同的对象(它们等价)。换言之,这两个对象是对相同对象的不同引用。为了判断对象是否相等,Smalltalk 提供了=方法。该方法通过比较两个对象中相应实例变量的值来实现。任何加入新实例变量的类都需要重新实现这个方法。例如,比较链表对象要涉及比较链表的长度,以及比较链表中的每个元素是否相等。这与递归导航整个对象树的深复制操作非常类似。与==对应的是~~,它用于判断两个对象是否不等价。与此类似,=对应的是~=,即不相等操作符。
C++C++与Smalltalk和Eiffel完全不同,这可以理解。C中定义的比较操作符是,它用于比较值(C 中不使用操作符来比较结构)。C++中并未定义默认的比较机制。在需要使用类的比较语义时,由设计者负责实现操作符== 和在重载操作符==函数中提供正确的比较语义。比较指针与比较整数类似,而且也是语言的一部分。
另一个需要实现的操作符是不相等操作符!=。如果类实现了,则最好也实现!=。成对实现操作符可以保证在比较对象时,两操作符中只有其中之一(或!=)为真。如果缺少其中一个,类的接口则看起来就不完整,而且即使使用另一个操作符更加切合实际,客户也只能被迫使用类所提供的不成对的操作符。
记住:
如果对象需要比较语义,要实现==操作符。
如果实现==操作符,记住要实现!=操作符。
还需注意,==操作符可以是虚函数(将在第5章中讨论),而!=操作符通常是非虚函数。
Smalltalk有一个与对象散列值(hash value)相关的概念。每个类都支持hash方法,作为类本身基本运算的一部分。该方法为每个对象都返回一个唯一的整数。任何相等的两个对象都会返回相同的散列值。但是,不相等的对象也可以(或不可以)返回相同的散列值。通常,该方法用于链表、队列等中的快速查找对象。即使 C++和 Eiffel 都未提供这样的方法作为语言的一部分,但许多商业软件产品仍提供hash成员函数。而且,在许多实现中,系统中的每个类都要求提供hash方法的实现。语言结构被用于强制执行这样的限制。有时,某些方法也要求强制执行这样的限制,如 isEqual()和 Clone()方法。决定哪些固有方法需要所有类的支持是设计的难点,任何设计团队都应在早期设计阶段处理这些问题。
无用单元收集问题所谓无用单元(garbage),是一块存储区(或资源),该存储区虽然是程序(或进程)的一部分,但是在程序中却不可再对其引用。按照 C++的规定,我们可以说,无用单元是程序中没有指针指向的某些资源。以下是一个示例:
main() { char* p = new char[1000]; // 分配一个包含1000个字符的动态数组 char* q = new char[1000]; // 另一块动态内存 // 使用p和q进行一些操作的代码 p = q; // 将q赋值给p,覆盖p中的地址 /* p所指向的1000个字符的存储区会发生什么?此时,p和q指向相同的区域, 没有指针指向之前p指向的旧存储区!该储存区还在,仍然占用着空间, 但程序却不可访问(使用)该区域。这样的区域则称为无用单元。*/ }