《C++编程风格(修订版)》——2.7 编程风格示例:第二种方法-阿里云开发者社区

开发者社区> 开发与运维> 正文

《C++编程风格(修订版)》——2.7 编程风格示例:第二种方法

简介:

本节书摘来自异步社区出版社《C++编程风格(修订版)》一书中的第2章,第2.7节,作者:【美】Tom Cargill,更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.7 编程风格示例:第二种方法

C++编程风格(修订版)
我们暂时先不去考虑去解决 string 类中的其他问题,而是将注意力转移到另一个不同的字符 串类。在这个类中,我们避免了大多数的上述问题。我们来分析程序清单 2.3 中的 SimpleString 类。 虽然 SimpleString 相对于 string 进行了改进,但仍然存在着一些缺陷。

程序清单 2.3 最初的 SimpleString 类
image
image

与 string 类一样,SimpleString 通过一个字符类型的指针 _string 和一个整数 _length 来表

示字符 串。当 SimpleString 对象不为空 时,数据成员 _length 表示的就是字符串的长 度。在 SimpleString 对象中可以不包含任何字符串,这是通过一个空的字符类型指针和零长度来表示的。 在 SimpleString 中,并不存在字符数组的长度多 1 或少 1、内存泄漏等问题,在类的表示和接口 中也不存在不一致的问题。然而,在 SimpleString 中还是存在着一些缺陷。

冗余
SimpleString 中的数据成员是以一致的方式来记录字符串的长度。_length 的类不变性需要包 括两种情况:如果 _string 是非空指针,那么 _length 就是字符串的长度;如果 _string 是空指针, 那么 _length 就应该为零。
image

在每次改变了 SimpleString 对象的状态之后,_length 的值都需要正确进行计算。然而,这 个长度信息却从来没有用到过。在 SimpleString 中,当每次需要字符串的长度时,这个值都将在 Strdup() 中重新进行计算。例如,在拷贝构造函数中,虽然 _length 的值是通过对参数进行复制 来得到的,但仍然需要在 Strdup() 中调用 strlen() 来计算字符串的长度。

在 SimpleString 的实现中,大概有 1/4 的代码需要用来维护 _length 的正确性。如果这些 代码并没有提供很有用的功能,那么应该从类中去掉它们。我们可以通过去掉 _length 来改进 SimpleString。

避免对从不使用的状态信息进行计算和存储。
如果在 SimpleString 中需要使用 _length 来避免重新计算字符串的长 度,那么情况将与 我们在前面讨论过的有所不同。虽然从信息理论上来看,_length 是冗余的,但它可以加快 SimpleString 的使用速度,从而能够提供有用的功能。因此,上面的这条规则不能解释为“尽可 能少地去存储信息”,它的确切含义是,只有当信息在后续操作中需要被用到时,才应该存储。

动态内存以及 operator=image

在 SimpleString 类中定义了两个赋值运算符:

虽然在 SimpleString 中没有了内存泄漏,但这两个运算符函数都可能会过早地删除了内存中的字符串。我们在 string::concat() 中已经看到了,在安排删除动态内存的时间上需要谨 慎。如果在赋值运算两边的操作数是同一个 SimpleString 对 象,并且调用了 operator=(constSimpleString&),那么这个表达式的结果将是未定义的。虽然程序员不大可能会显式地写出像 x=x 这样的表达式,但在程序中可能会间接地导致这种赋值运算的发生。如果 a 和 b 碰巧都是引用了同一个 SimpleString 对象,那么 a=b 就等价于是 x=x。无论是何种情况导致了这种赋值运算, 如果在调用 operator= 时,函数参数与 this 指针所指向的是同一块内存,那么在旧的字符串作为 参数传递给 Strdup() 之前,就已经被删除了,因此返回的结果是未定义的。正确的方法是将参数 与 this 的值进行比较,如果二者相等就不进行任何操作。
image

在定义 operator=时,我们要注意 x=x这种情况。
对于 operator=(const char*) 中的 delete 操作,可能会产生问题的情况是 x=x.string(),或者虽 然以 a=b.string() 的形式出现,但 a 和 b 中的字符指针指向的是同一个地址。在执行 x=s 的赋值运 算时,如常量字符类型指针 s 的值等于 x.string(),那么将会产生和 operator=(const SimpleString&) 中同样的问题。再次指出,无论是上述何种情况,如果在调用 operator= 时,函数的参数指针等 于将被删除的字符串,那么 Strdup() 的结果将是未定义的。对于 string::concat() 函数来说,解决 的方法是将 delete 操作推迟到字符串被复制之后再进行。
image

注意在上面两个运算符函数中代码的相似性。这两个函数都是先拷贝一个字符串,然后删 除原有的字符串。因此,我们应该在一个运算符函数中调用另一个运算符函数,而不是在两个不 同的地方进行相同的工作。就像我们在 string 类中用一个带有默认参数的构造函数来代替最初的 的两个构造函数一样,我们没有理由为同样的代码维护两份拷贝。

最后一个细节问题
对 SimpleString 的最后一个改进是关于辅助函数 Strdup(),这个函数的作用是对字符串进行复制并存储在一块新分配的内存中。Strdup() 中的问题并不在于一致性,而是在于 SimpleString 类中所有调用 Strdup() 的地方,调用代码都遵循着同样的形式:在每次调用之前,都要进行测 试以保证参数指针是非空的;在从 Strdup() 中返回之后,返回结果都是保存在 _string 中。因此, 我们可以对类的实现进行简化,用一个私有成员函数来代替 Strdup(),我们在这个私有函数中首 先对参数指针进行测试,然后对字符串进行拷贝,最后在 _string 中存储新的指针值。这个修改 是通过将重复的代码放到一个通用的函数中以消除重复代码。我们在这里所应用的规则来自于 Kernighan 和 Plauger 的著作 [ 第 15 页 ] :

用一个通用的函数来代替重复的表达式序列。
程序清单 2.4 中给出了改写之后的 SimpleString,其中包含了我们在上面所讨论的修改以及 其他的一些改进。

程序清单 2.4 改写之后的 SimpleString
image

本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享:
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

其他文章