文章

重构drawing时的一些想法

重构drawing时的一些想法

[TOC]

一、is-a 和 has-a

这两者远比看上去的要复杂。

首先明确一下两者的区别:

  • 继承:is-a
  • 关联:has-a
  • 聚合:has-a(整体和部分可以分开,比如电脑和CPU、猫和动物)
  • 组合:has-a(整体和部分不可以分开,比如公司和部门,没有公司,部门就不能存在)

举一个实际遇到的例子:

现在的 PlanManager 项目中,tpDetail(大样类) 和 tpLabel(文字标注类) 的关系就有异议:

丛工认为这两者应是继承于同一个基类的,因为他认为大样图也是标注的一种,应该和文字标注从同一个父类 tpBiaoZhu 中继承出来,然后把公共的成员变量提到 tpBiaoZhu 中,也就是文字和引线。这样就会导致 tpLabel 是空的,tpDetail中还是保持不动。

我、高工、常工则认为 tpDetail 和 tpLabel 不存在继承关系。因为大样图中是一定会有文字标注的,这二者是包含关系。这二者是各自一个单独的类,且 tpDetail 中会包含一个 tpLabel 的对象来表示大样图中的文字标注。

经过讨论最终丛工接受了我们的观点。(毕竟我们人多)

由上面的例子可以想到,其实两个类是否应该是继承关系,不能仅仅停留在个人理解上,而是要考虑类之间的包含关系和共同性是否足够。


二、类内

1. 类内函数

  • 类内函数分类:

    一个可以 实例化对象的类类内函数要明确分为 如下两种:

    • 成员函数:直接操作成员变量、成员变量的赋值和取值。
    • static 纯方法:用来把一些通用的、与成员变量交互少的方法单独包出来。
  • 函数声明:

    任何类内的函数,均应合理使用 public、protected、private 声明:

    • private:新建一个函数时默认为 private,会被子类调用时改为 protected,会被外部调用时改为 public。
    • protected:不会被外部调用,会被子类的成员函数调用的函数。
    • public:会外部调用的函数。
  • 参数:

    入参 应加 const出参& 修饰。

    给老函数新增参数的时候,应增加在最后且赋默认值,否则 难以保证兼容所有老的调用

  • 函数修饰符:

    使用以下修饰符,使代码可读性更高:

    • override:重写基类虚函数时,应在函数声明处添加,以区分 virtual 函数的重写或新增。

    • 纯虚函数:当前类不会实例化对象,且要求子类必须实现此函数时,应将该函数声明为纯虚函数,例如:

      1
      
      virtual int Kind() = 0;    // 子类的类型,用于区分各子类
      

2. 成员变量

成员变量不应是 public 类型的,默认应为 private ,有子类使用到时再改为 protected 。

成员变量存在的意义只应是存储信息。 每个成员变量应该是有具体含义的。

换句话说,以下类型是 不应 存在于成员变量中的:

  • 类内各函数都会用到的公用参数,但是没有实际意义。例如 PlanManager中一些类中的m_Para指针,仅仅是为了取参数方便。
  • 函数与函数之间的传参,也就是计算过程中的临时变量。

3. 一些特殊用途的类

有些类的用途就不是用来实例化对象的,例如:

  • 纯虚类,用作接口供其他项目调用。
  • 枚举类,用来存储一些固定的、有限的可选值。
  • 工具类,用来存储一些通用的、与成员变量交互少的 static 函数。
  • 命令分发类,用来把命令分发给各个处理类。
  • 单例类,用来存储一些全局的、唯一的对象。

三、关于裸指针

直接操作裸指针是一件非常危险的事情,但是 C++ 中又会不可避免的直接操作裸指针。

裸指针在使用时需要注意的地方:

  1. 避免成为野指针。

    野指针:一个指向无效内存位置的指针

    产生原因: 声明指针时未初始化。 delete 后没有将该指针赋为 nullptr,且后面又继续使用该指针。(这种情况下的指针也被特称为 悬空指针

  2. 储存在哪里,生命周期是多久?

    • 临时变量。这种情况很容易导致内存泄漏,避免方法见上面的内容。

    • 存在某个实例对象里。只要实例对象里能正确的管理好指针,就不会出问题。但前提是这个实例化的类可以正确的管理这个指针。

  3. 裸指针做临时变量时:

    例如当使用裸指针做临时变量时,可以用一个 while(true) 循包住数据处理的内容,有异常就从 whilebreak 出来,保证不管咋样都是能在最后 delete 指针的:

1
2
3
4
5
6
7
8
9
10
int nType = 9;     // 定义数组大小
float* fDiaBar = new float[nType];   // new一个数组指针
while(true)
{ 
    // 从pRCS中取数据:
	pRCS->GetMainBar(fDiaBar, nType);    
    /*这里做一些其他数据处理,有异常就break*/ 
    break;         // 最后退出while
}
delete[] fDiaBar;  // 最后delete数组指针
  1. 裸指针要交给某个类时:
    • 裸指针最好时以成员变量的方式存在于某个类内,由类的析构函数负责 delete,由类内函数处理裸指针的赋值、拷贝和替换。

    • 当一个指针从 new 出来,到交给 管理类 之前,期间的操作也应和上面的代码片段一样,把中间操作的内容放到一个 while(true) 循环中,有异常就 break 出来,保证不管咋样都是能在最后指针交到 管理类 手里的。
    • 或者是 new 出来就立刻交给 管理类 ,然后再针对这个指针该干啥干啥,这样即使有异常导致跳出,这个指针也有人负责了。
本文由作者按照 CC BY 4.0 进行授权