本节书摘来自异步社区出版社《C++编程风格(修订版)》一书中的第2章,第2.5节,作者:【美】Tom Cargill,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.5 动态内存的一致性
C++编程风格(修订版)
在程序清单 2.2 的 string 类中仍然存在着一些问题和不一致的地方。其中,在动态内存管理 上的不一致性与我们在前面所看到的不一致性是一样的,都是严重的问题。对于所有动态分配的 内存,我们都需要回答两个问题:首先,动态内存是不是足够大以容纳将要存储的信息?其次, 是不是所有的动态内存都是可回收的?
在默认构造函数中分配的字符数组肯定可以容纳空字符串:
这个构造函数所基于的假设是:在创建对象时将会为字符串分配内存,并且这个内存足以 容纳在对象生存期内需要保存的任意字符串。成员函数 assign() 与这个假设也是一致的:
在 assign() 中调用 strcpy() 对参数字符串进行拷贝时,并没有考虑到目标字符数组的长度或 者大小。编写客户代码的程序员必须保证——在创建对象时,无论调用的是哪个构造函数——在 构造函数中所创建的数组必须能够容纳在 assign() 中复制的任意字符串。
然而,在成员函数 concat() 中采用了一种不同的方法:在创建每个字符串时,总是动态地决 定所需数组的精确大小。函数 concat() 忽略了在创建 string 对象时已经分配好的字符数组,即使 这个已分配的数组是足够大的:
在 assign() 和 concat() 这两个函数的表现行为上存在着不一致性。它们的区别在于,在为 string
对象设置新值时,是否会动态分配字符数组:assing() 永远不会分配,而 concat() 则总是会分配。
接口一致性
上面哪种控制数组大小的方法是更好的?和许多软件决策一样,没有哪种方法是绝对的“正 确”或者绝对的“错误”。这两种方法都有各自的优点。保持在构造函数中分配的数组不变(assign() 中的做法)是一种高效的方法,因为在后续的操作中就无需再调用内存分配函数。对每个字符串 值都动态地决定数组的大小(concat() 中的做法)则是一种更安全的方法,因为这种方法杜绝了 数组的“越界”行为。
这两种方法都可以用在类中,但我们只能使用其中的一种,以保持类一致性,而不应该将 这两种方法混合使用。否则,在使用这个类时,程序员将不得不去了解在接口中不同操作之间的 不同约定。如果一个程序员只使用过 concat(),并且知道了数组的大小是动态增长的,那么他就 会假定 assign() 也是同样的行为,因此,当在 assign() 中发生数组内存的越界问题时,他所感到 的沮丧应该是可以预见的。
类的接口定义应该是一致的——避免产生困惑。