《Effective C++》重点摘要(六)

《Effective C++》第六章:继承与面向对象设计

  1. 确定你的public继承塑模出is-a关系。student is a person,所以student可以public继承自person。概念上,正方形是长方形,但是如果让square继承自rectangle呢,square需要像rectangle那样具有长和宽两个成员变量吗?如果是,每次改变正方形的宽的时候,也要改变正方形的长,这很奇怪不是吗?所以is-a关系不仅仅是现实概念上的,也是实现上的,确保对于base class对象身上发生的每件事对derived class对象而言是自然的,因为public意味着is-a关系。
  2. 避免遮掩继承而来的名称。public继承时,如果derived class内有与base class同名的函数,是同名,不管是否virtual、参数类型、常量性,那么derived class中的函数都会屏蔽掉base class中的那些(不是一个,而是所有)函数,原因是编译器解析函数名称时的查找规则。屏蔽base class中的函数往往不是我们想要的,因为derived class object is-a base class object。解决方案是在derived class声明中加上语句using::member_function_name;只是member_function_name没有其他。
  3. 区分接口继承和实现继承。以下几条法则针对public继承有效:

    1) 成员函数的接口总是会被继承。

    2) pure virtual函数的目的是为了继承接口。

    3) impure virtual函数的目的是为了继承接口和一份缺省实现,derived class可以改写实现。

    4) non-virtual函数——在不违反上一条的情况下——则是为了继承接口与一份不可更改的实现。

    设计合理的接口,选择需要的继承类型。

  4. 考虑virtual函数以外的其他选择。为了表现多态,除了简单的virtual函数的继承外,还可以有另外的选择,它们可以是:

    1) non-virtual interface手法实现的Template Method(这是指设计模式中的模板方法,不是C++中的模板)。这可以避免许多重复的代码,仅仅把non-virtual interface中变化的一小部分做成virtual就行了。

    2) strategy模式。C++拥有多种编程范式,所以实现strategy的方式很多。可以使用设计模式中的传统strategy方法(藉由多态),也可以利用函数指针,还可以使用functor(仿函数),还可以使用委托,嗯?C++中的委托?你可以模拟,利用模板,我前面的博文利用C++11的新特性简单模拟了一个C++11模拟C#委托,C++98嘛,那要花费很大的心力了。委托可以利用函数指针和functor,区别是委托比这两位更灵活。

  5. 绝不重新定义继承而来的non-virtual函数。好了,这和前面的内容一致了,记得函数接口的四种语义吗?再说一遍,设计合理的接口,选择需要的继承类型。
  6. 绝不要重新定义继承而来的缺省参数。遵循上一条,就让这一条涉及的面缩小了一点,virtual表示动态的(取决于运行时的实际类型),而缺省意味着静态的(编译时确定的),如果你想让一个动态的接口拥有静态的参数,当你使用多态时,在派生类中缺省参数无论你如何改写,都是来自基类的,而运行的函数则是derived版本。如果想改写缺省参数,那结果肯定不是想要的。最好考虑一下使用替代的方法改写virtual接口,如果不能,考虑一下设计是否恰当。总之不要重新定义继承而来的缺省参数,重定义没有效果。
  7. 通过复合塑模出has-a语义或”根据某物实现出“语义。A拥有B,那就使用复合,A的一些操作需要B的一些操作完成,但是B is not a A,也可以使用复合,尽量避免不要使用继承,这会为类设计带来低内聚和紧耦合的问题。复合的形式可以是A类中含有一个B类成员,也可以是A类中某个成员函数使用了B类对象作为参数。
  8. 明智而审慎地使用private继承。如果使用private继承,编译器不会为derived class转换为一个base class;从base class中继承而来的成员(函数和变量)都会变成private的。这表示继承来的东西只是一些实现层面的,不会被外部访问,所以不会涉及设计层面,而根据上一条,这种关系最好使用复合替代。如果在考虑了各种其他替代方案之后,还是需要用private继承的话,才能使用。
  9. 明智而审慎地使用多重继承。多重继承有用,但是多重继承往往比单一继承复杂,并且容易导致歧义产生概率的增高(还记得钻石型结构吗?),virtual继承是解决歧义的一种方法,但是却不得不为此付出空间、效率、初始化复杂度的代价,对于virtual base class尽量不用,如果用了,也不要再其中放置数据成员。多重继承的一个应用场景是“public继承自某个interface class”与“private继承自某个协助实现的class”的两相组合。
时间: 2024-08-01 05:05:13

《Effective C++》重点摘要(六)的相关文章

BDD in Action 重点摘要

BDD in Action 重点摘要: BDD in Action ?对于需灰度发布的产品, 开发人员可经由BDD, 便可自保证需灰度发布产品的质量? ?所以, BDD 可使产品更快速的达到灰度发布的要求, 而可更快速的获得使用者对产品的反馈? ?在这样的开发模式下, 测试人员可更专注于探索性测试, 挖掘出更深层的产品缺陷, 回归到真正测试人员的专业?

《Effective C++》重点摘要(五)

<Effective C++>第五章:实现 尽可能延后变量定义式的出现时间.只有变量在恰好要使用之前定义,程序的可读性往往会得到提高,因为这样不容易忘记变量说代表的意思.另一方面,这样做可以提高程序性能,如果不需要一个变量时却要为它分配.释放空间,调用构造.析构函数,获取.释放资源--这,真是太浪费了.补充一点,声明式并不会做这些事情,所以可以考虑用声明式替换定义式以尽量延后变量的定义. 尽量少做转型动作.转型意味着出错的可能性大大提升,转型意味着更多的操作.如果不得不转型,考虑使用C++ s

《Effective C++》重点摘要(八)

<Effective C++>第八章:定制new和delete 了解new-handler的行为.new和delete不是函数,是申请和释放内存的操作符.当new提出获得内存申请失败时会发生什么?老旧的编译器是返回null指针.现在呢,如果申请失败,会先调用一个错误处理函数,那就是new-handler.这就像一个回调函数,系统有一个默认的,用户也可以自行编写一个错误处理函数并使用set_new_handler函数设置之.通常自行编写的错误处理函数可以使用这些策略: 1) 多次尝试申请内存.

《Effective C++》重点摘要(九)

<Effective C++>第九章:杂项讨论 不要轻忽编译器的警告.严肃对待编译器发出的警告信息.努力在最高警告级别下争取无警告.也不要过度依赖编译器给出的警告信息,不同的编译器给出的警告信息是不一样的,甚至给出警告的点也不一样. 让自己熟悉包括TR1在内的标准程序库. 让自己熟悉Boost. C++不想Java和C#那样,语言本身就带有很强大的库,所以外部的程序库对于编写C++程序至关重要.俗话说不要重复发明轮子嘛,所以能用别人的就用吧.当然这是在实际工程中,学习阶段嘛,我还是比较推崇自己

《Effective C++》重点摘要(一)

开篇的话 这个星期不再发布关于数据结构的博客,想把半个月来看的书做一些总结,整理整理,第一本就是<Effective C++>.第一次看这本书是一年多前,准备考研复试的时候,随后陆陆续续,这个月再来看算是第三遍了吧,之前没有看过<深度探索C++对象模型>,所以有的地方看得不是很透彻(现在有的地方也看得不透,但是比以前好点了).还有另一本书就是<深度探索C++对象模型>,看第二遍,吸收得更容易了,看书总是这样,第一遍也许什么也不知道,但是只要坚持看下去,回过头来再读的时候

《Effective C++》重点摘要(二)

<Effective C++>第二章:构造/析构/赋值运算 C++默认编写的函数.C++编译器如果没有发现以下函数,就会为类生成一份默认版本的: 1) default构造函数 2) default析构函数 3) copy构造函数 4) copy assignment操作符(=运算符) 前两个函数并不总是产生,它只在编译器需要的时候才产生出来.后两个函数只保证以bitwise语义拷贝non-static成员,所以如果有指针.引用等non-static成员的类要么拒绝编译器产生的版本,要么自行小心

《Effective C++》重点摘要(三)

<Effective C++>第三章:资源管理 以对象管理资源.一份资源,可能是一片内存,可能是一个锁,当客户申请后需要手动释放才是合理的,那么最好在获得这份资源的时候,就立刻把它放到一个对象里(RAII技术),然后在对象的析构函数里释放它.这样释放操作就不会被遗忘了,并且,就算在使用资源类的语境中有语句抛出异常,也能确保资源得到正确的释放. 在资源管理类中小心coping行为.有的资源是不支持copy语义的,而有的资源需要深拷贝语义,有的资源支持控制权转移语义,还有的资源是支持引用计数的,所

《Effective C++》重点摘要(七)

<Effective C++>第七章:模板与泛型编程 了解隐式接口和编译期多态.面向对象编程总是采用显式地声明一个接口,并在子类中提供特殊的实现,进而实现运行期多态.模板类中的接口往往是隐式的,隐式的意思是,编写模板时,假设需要的接口类T中是存在的,至于实例化模板的类是否真的具有该接口,编译时才知道(如果实例化类型没有实现这些接口,则编译失败).这正是模板多态的展现方式,由实例化模板的类决定具体的行为是什么.这种多态编译期与运行期多态有很大的区别,运行期的多态是一种is-a类型的多态(我自造的

《More Effective C++》重点摘要一:基础议题

仔细区别pointers和references.指针和引用有些相似,他们本身都是对存在于某个地方的对象(不是指class)的指示,但是他们有着本质的区别.指针变量存储所指对象的地址,所指的对象可以是null,只要可以寻址就行.而引用是某个已经存在对象的别名,所以不可以先声明一个引用,经过一段时间(代码)后让它指向某个对象. 最好使用C++转型操作符.C++提供了自己的四种转型操作符: 1) static_cast.拥有与C旧式转型相同的意义与限制,但不能移除表达式的常量性,因为有const_ca