1、考虑下面的需求,软件处理动物,Cat与Dog需要特殊处理,因此,设计Cat和Dog继承Animal。Animal有copy赋值(不是虚方法),Cat和Dog也有copy赋值。考虑下面的情况:
Cat cat1;
Cat cat2;
Animal *a1 = &cat1;
Animal *a2 = &cat2;
*a1 = *a2;
思考*a1 = *a2会有什么问题? copy赋值不是虚方法,根据表面类型,调用Animal的copy赋值,这就导致所谓的部分赋值,cat2的Animal成分赋值给cat1的Animal成分,二者的Cat成分保持不变。

2、怎么解决上面的问题?
将Animal的copy赋值声明为virtual方法,如下:
virtual Animal& operator=(const Animal &rhs);
Cat和Dog重写:
virtual Cat& operator=(const Animal &rhs);
virtual Dog& operator=(const Animal &rhs);
这里使用了C++语言后期的一个特性,即协变,返回的引用更加具体。但是,对于形参表,重写必须保证保持一致。将copy赋值声明为virtual,解决了部分赋值的问题。但是,引入了一个新的问题。如下:
Cat cat;
Dog dog;
Animal* a1 = &cat;
Animal* a2 = &dog;
*a1 = *a2;
这是异型赋值,左边是Cat,右边是Dog。C++是强类型语言,一般情况下,异型赋值不合法,不会造成问题。但是,这种情况下导致异型赋值合法。对于指针解引用的情况,我们期望同型赋值是合法的,异型赋值是非法的。容易想到的办法是,在重写的copy赋值中,使用dynamic_cast进行同型判断。比如Cat的copy赋值,首先判断rhs是不是Cat,如果是,就赋值,如果不是,抛出异常。
我们知道,使用dynamic_cast效率低,考虑下面的情况,cat1 = cat2; 即使cat1与cat2的表面类型就是Cat,也会调用Cat& operator=(const Animal &rhs),进行一次dynamic_cast的运算,这不是我们所期望的。解决办法是:增加一个过载方法,编译器编译时,根据表面类型确定方法的调用。如下:Cat& operator=(const Cat &rhs)。同时对于重写的方法,可以调用前面的方法,如下:
Cat& operator=(const Animal &rhs)
{
return operator=(dynamic_cast<Cat&>(rhs));
}

3、运行期的类型检查,dynamic_cast的使用应该尽量避免。因为,首先效率低,其次,有些编译器还不支持dynamic_cast,不具有移植性。有没有更好的办法?
导致问题的原因是,对于指针解引用的赋值,父类的copy赋值不是虚方法,导致部分赋值。
因此,解决办法是,提取一个抽象类AbstractAnimal,将copy赋值声明为protected,子类可以调用,表面类型是抽象类的指针解引用赋值,不能调用。增加一个Animal类,继承AbstractAnimal。
对于抽象类,内部至少要有一个纯虚方法,很自然地将析构方法声明为纯虚方法。对于纯虚方法,需要注意:
a、纯虚方法意味着当前类为抽象类,不能实例化。
b、纯虚方法要求子类必须重写。
c、特别注意,纯虚方法一般不提供实现,但是允许提供实现,子类也可以调用。如果析构方法为纯虚方法,必须要提供实现。因为子类调用自身的析构方法后,必定会去调用父类的析构方法。

4、考虑,具体基类没有字段,是不是就不需要上述的抽象类了?这有两个问题,首先现在没有字段,以后可能会有字段,其次如果一个类没有字段,一开始就应该是一个抽象类。

5、结论,对于继承体系中的非尾端类,应该设计为抽象类,如果使用外界的程序库,需要做一下变通。

【M33】将非尾端类设计为抽象类的更多相关文章

  1. Java集合为什么设计为:实现类继承了抽象类,同时实现抽象类实现的接口

    更好阅读体验:Java集合为什么设计为:实现类继承了抽象类,同时实现抽象类实现的接口 问题 Java集合源码为什么设计为:「实现类继承了抽象类,同时实现抽象类实现的接口?」 看着List 集合的UML ...

  2. python基础----继承与派生、组合、接口与归一化设计、抽象类、子类中调用父类方法

    一.什么是继承                                                                          继承是一种创建新的类的方式,在pyth ...

  3. python基础之类的继承与派生、组合、接口与归一化设计、抽象类、子类中调用父类方法

    一.什么是继承 继承是一种创建新的类的方式,新建的类可以继承自一个或者多个父类,原始类称为基类或超类,新建的类称为派生类或子类. 派生:子类继承了父类的属性,然后衍生出自己新的属性,如果子类衍生出的新 ...

  4. Java11-java基础语法(十)类设计综合案例

    Java11-java语法基础(十)类设计综合案例 一.类综合设计方法 1.类设计步骤 (1)分析数据成员 (2)分析成员方法和构造方法 (3)画出类图 (4)编码测试 2.具体问题 1)分析数据成员 ...

  5. iOS控制器之基类设计

    题记 在进入新公司后.经过这一个月的重构项目,终于把项目做到了个人相对满意的程度(还有一种不满意的叫老板的需求,提过多次意见也没用= =!).在这次重构中按照以前的思路设计出了个人觉得比较适用的一个基 ...

  6. 水果项目第1集-想法>需求->功能->数据库设计->类设计

    懒,懒人,我是个懒人. 懒人想做点事,总是拖拖拉拉,迟迟没有开始. 很久很久以前,就想做属于自己的产品,但是至今还没有一个属于自己的产品. 两年前,终于想好,要做一个网上卖水果的系统,手机上点点,水果 ...

  7. 非Controller类无法使用Service bean解决方案

      尝试方案: 1 在Spring的配置文件springmvc.xml中,增加扫描项base-package="zxs.ssm.util",增加你需要使用service的类所在的包 ...

  8. [theWord] 一种英文字典的基类设计

    theWord --- 一种英文字典的基类设计 使用场景 想写一个应用,来记录自己背单词时候,对每个单词的记忆状况之类的东西.至于为什么做这个,试过了一些背单词软件,并不觉得好用,自己做一个吧. 那么 ...

  9. c++特性:指向类成员的指针和非类型类模板参数和函数指针返回值 参数推导机制和关联型别

    一.c++允许定义指向类成员的指针,包括类函数成员指针和类数据成员指针 格式如下: class A { public: void func(){printf("This is a funct ...

随机推荐

  1. ylb:SQL 常用函数

    ylbtech-SQL Server: SQL Server-SQL 常用函数 1,数学函数 2,日期和时间函数 3,字符串函数 4,转换函数 1,ylb:SQL 常用函数返回顶部 1,数学函数 2, ...

  2. MySql通用分页存储过程

    MySql通用分页存储过程 1MySql通用分页存储过程 2 3过程参数 4p_cloumns varchar(500),p_tables varchar(100),p_where varchar(4 ...

  3. Getting and installing the PEAR package manager

    Windows After you have downloaded and installed PHP, you have to manually execute the batch file loc ...

  4. Android 者开发如何选择测试机列表

    Android 系统已经分化成多种不同的定制版本,制造厂商的不同手机使用的硬件千差万别.差异化带来良好的用户体验的同时,也给开发者带来的适配的问题.于是每个开发团队都需要面临选择测试机列表的问题.我基 ...

  5. python中隐式的内存共享

    在python中,基本上使用的是引用,那么就会造成一个隐式的内存共享,特别是在容器对象中,例如list,dictionary 对于不可变对象,是不会造成隐式的内存共享情况,如下所示: >> ...

  6. 【LeetCode】242 - Valid Anagram

    Given two strings s and t, write a function to determine if t is an anagram of s. For example,s = &q ...

  7. html5_common.js

    (function(){ this.sendAjax = function(url,func,formData,type){ type = type || "POST"; //默认 ...

  8. linux 操作总结

    1.集群节点之间ssh无密码登陆 参考:http://www.cnblogs.com/jdksummer/articles/2521550.html 解决: 1)删除旧有记录:rm -r ~/.ssh ...

  9. Application_Error

    //出现未捕捉的异常时,系统调用本方法,一般用于记录日志.错误页的重定向一般在web.config中设置.        protected void Application_Error(object ...

  10. 第二百一十三天 how can I 坚持

    今天去爬山了,百望山,山好矮,绕来绕去也挺远的,山上有座庙,佘太君庙,还有个望京楼,还看到了个写书法的,不懂,但很崇拜. 今天玩LOL全输了,咋搞的呢. 还有买了点排骨,都没吃完. 下周去爬凤凰岭. ...