当需要为一个既存的类中增加一个数据成员的时候,你会想到那些问题?

来源:互联网 发布:ubuntu xfce4 美化 编辑:程序博客网 时间:2024/06/04 18:50
转载请标明出处:http://blog.csdn.net/zhangxingping
    前几日,在调试程序的时候发现有对象的枚举类型成员变量的值为0xcccccccc,这个数值显然是不正确的,因为对应枚举的定义如下:
    typedef enum enDirection    {        enDirectionUnknown,        enDirectionUp,        enDirectionDown,        enDirectionLeft,        enDirectionRight,    }enDirection;
    可见该变量合理的取值应该是在0,1,2,3或者4。
    从枚举取值的含义来看,0表示未知的方向,其他枚举值的含义非常明确。那么在不能确定方向的时候,该变量的值应该为enDirectionUnknown更为合理一些。
当时想到的第一个问题就是初始化,因为该成员变量是后来增加的,很有可能没有对其进行初始化,导致某些情况下在使用改变量之前一直都未对其进行初始化,所以其值比较随机。查看类的两个构造函数,都没有看到对该成员变量进行初始化的语句。

总结一下这个小问题:当需要为一个既存的类中增加一个数据成员的时候,应该考虑到那些问题呢?我想至少应该包含以下几个方面的考虑:
1. 访问权限
   面向对象的一个重要特性就是封装。数据成员的访问权限就是这种特性的一个表现。从C语言的角度来讲,访问权限类似于是一种作用域。实践中,常见的做法是为了避免数据封装后需要编写相应的访问函数,便将数据的权限设定为public。这种做法虽然简单,却严重破坏了封装性所带来的好处(这些好处就不说了,大家都懂得的)。可见不假思索地设置数据成员的访问权限为public是一种“懒惰”的做法。就像C语言的代码中,当需要增加变量的时候,不假思索地将它增加为全局变量一样。虽然用起来方便了,但是这种做法对于C来说严重破增加了代码的耦合性,增加了代码的维护性成本。所以恰当的访问权限是良好C++代码的一个重要指标。笔者所遇到的那个枚举类型的变量就是被设置为public的。该变量实际上表示的某个检测算法检测的结果。从实际含义来看,该数据成员对外界来说显然应该是只读的,不可能允许外界修改。因此,设置其为private,并编写对应的Getter方法,而不提供setter方法则更合理一些。
2. 构造函数中的初始化
   总所周知,使用没有初始化的变量后果是不堪设想的,编译器一般也会对这种情况报告warning的。但是实际中,一般只会在局部的范围内这种意识才会生效,比如某个函数的内部,或者循环体的内部等。当数据的作用域跨越多个函数的时候,比如类的数据成员或者是全局变量,这种意识显然就会变得没有那么强烈了,甚至往往会被忽略。一般来说,对于类的数据成员,其初始化就是在构造函数中进行的。如果有多个构造函数,那么这多个构造函数中都要确保对所有的数据成员进行了正确的初始化。由于函数重载特性的存在,类中的构造函数的参数类型和个数也不尽相同。如果有可用于对该数据成员进行初始化的参数,那么可以使用这些参数来对数据成员进行初始化;如果没有可用参数,那么数据成员也应该有缺省的初始化值,而不是置之不理,不对其进行初始化。笔者所遇到的情况就是没有对其进行初始化。诸如检测结果之类的数据成员,在构造函数中如果没有可能的参数,其初始化值理应为unknown较为合适。之所以说构造函数中应该对所有的数据成员都进行初始化,是为了确保经过该构造函数生成的对象是完全可用的,而不是部分可用的。这也许是C++中构造函数总会在生成对象的时候被调用的原因吧。试想,如果把生成对象和对象的初始化分开成两步进行, 那么这两步之间就有可能出现使用该对象的代码,就造成了使用没有初始化对象的可能。
3. 析构函数中是否需要释放对应的资源
    在C++中,与构造函数相对应的就是析构函数了。通常他完成的都是一些善后的工作,回收资源等等。对于新增加的数据成员,也应该考虑是否需要在析构函数中对其进行相应的处理。比如,指针成员指向的空间的释放,文件资源是否关闭等。析构函数中不一定要向构造函数那样对每一个数据成员都进行对应的操作,但是是否需要进行相应的操作是必须考虑的。
4. 该数据成员是否和别的数据成员有依赖关系
    类中的某些数据成员直接可能存在某种特定的依赖关系。当其中的一个发生了变化,关联的其他数据也要随之而变动。比如,员工管理系统中员工的ID可能是由如下规则构成:姓名首字母+编号,中文姓名为拼音。这样当编号的数据发生变化的时候,这个唯一的ID信息也就要随之发生变化了。因此,在为该既存的的类中增加数据成员的时候,需要考虑增加的数据与既存的数据之间是否有某种关联。实际中可能没有关联,但是这种考虑是必须的。
5. 数据的读写
    上述的第一条中从外界的角度来考虑了数据的可访问性。这里从内部的角度出发考虑新增的数据在何时何处被读写。当然,前面提到的初始化就是写数据的一种特殊情况。一般的数据成员的读写是根据实际需要来进行的,没有一定的模式可以套用。但是由于类的普通的数据成员在类的所有方法中都是可以被访问的,因此必须考虑到所有的方法中哪些方法需要读该数据,而哪些方法需要写该数据的,还有哪些方法中是既需要读取,又需要改写的。这种考虑是非常重要和必要的,也是维护数据正确的关键。另外,还要考虑多线程的情况。有时,某些数据是需要在一个线程中读取,而由另外的线程改写的。
  
    以上就是笔者对于题目中问题的思考,如有不尽之处,欢迎补充,期待您的分享。

    另外,说一下对枚举类型变量的取值:由于枚举类型中已经定义了所有可能的取值,因此枚举类型变量的取值就应该局限于该定义,不应该超越列出的可能的取值范围。
    关于初始化的一个Tip:一般情况下对于新分配的内存都是使用memset将其全部初始化为0,但是这也是需要谨慎考虑的。笔者就遇到过对一个结构体进行memset为0导致程序异常的情况。因为结构体中有个成员表示状态,而0恰巧就是一种有效的状态。简单对结构体初始化为0导致结构体数据被认为是有效的,进而处理导致异常。

    程序开发是细节决定成败。成功找理由,失败找借口。 与君共勉。
   
原创粉丝点击