<Effective C++>读书摘要--Inheritance and Object-Oriented Design<二>
<Item 36> Never redefine an inherited non-virtual function
1、如下代码通过不同指针调用同一个对象的同一个函数会产生不同的行为The reason for this two-faced behavior is that non-virtual functions like B::mf and D::mf are statically bound (see Item 37). That means that because pB is declared to be of type pointer-to-B, non-virtual functions invoked through pB will always be those defined for class B, even if pB points to an object of a class derived from B, as it does in this example.
Virtual functions, on the other hand, are dynamically bound (again, seeItem 37), so they don't suffer from this problem. If mf were a virtual function, a call to mf through either pB or pD would result in an invocation of D::mf, because what pB and pD really point to is an object of type D.
class B {
public:
void mf();
...
};
class D: public B {
public:
void mf(); // hides B::mf; see Item33
...
};
D x; // x is an object of type D
B *pB = &x; // get pointer to x
pB->mf(); // calls B::mf
D *pD = &x; // get pointer to x
pD->mf(); // calls D::mf
2、If reading this Item gives you a sense of déjà vu, it's probably because you've already read Item 7, which explains why destructors in polymorphic base classes should be virtual. If you violate that guideline (i.e., if you declare a non-virtual destructor in a polymorphic base class), you'll also be violating this guideline, because derived classes would invariably redefine an inherited non-virtual function: the base class's destructor. This would be true even for derived classes that declare no destructor, because, as Item 5 explains, the destructor is one of the member functions that compilers generate for you if you don't declare one yourself. In essence, Item 7 is nothing more than a special case of this Item, though it's important enough to merit calling out on its own.
3、Things to Remember
Never redefine an inherited non-virtual function.
<Item 37> Never redefine a function's inherited default parameter value
4、virtual functions are dynamically bound, but default parameter values are statically bound.(For the record, static binding is also known as early binding, and dynamic binding is also known as late binding.)
5、An object's static type is the type you declare it to have in the program text.An object's dynamic type is determined by the type of the object to which it currently refers. Dynamic types, as their name suggests, can change as a program runs, typically through assignments.
6、Why does C++ insist on acting in this perverse manner? The answer has to do with runtime efficiency. If default parameter values were dynamically bound, compilers would have to come up with a way to determine the appropriate default value(s) for parameters of virtual functions at runtime, which would be slower and more complicated than the current mechanism of determining them during compilation. The decision was made to err on the side of speed and simplicity of implementation, and the result is that you now enjoy execution behavior that is efficient, but, if you fail to heed the advice of this Item, confusing.默认参数只是语法糖,在编译期已经展开
7、When you're having trouble making a virtual function behave the way you'd like, it's wise to consider alternative designs, and Item 35 is filled with alternatives to virtual functions. One of the alternatives is the non-virtual interface idiom (NVI idiom): having a public non-virtual function in a base class call a private virtual function that derived classes may redefine. Here, we have the non-virtual function specify the default parameter, while the virtual function does the actual work。
例如下面的代码存在代码重复和而且存在代码依赖,Shape 和Rectangle的draw函数默认参数必须一致,产生了依赖
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
virtual void draw(ShapeColor color = Red) const = ;
...
};
class Rectangle: public Shape {
public:
virtual void draw(ShapeColor color = Red) const;
...
};
可以做如下修改
class Shape {
public:
enum ShapeColor { Red, Green, Blue };
void draw(ShapeColor color = Red) const // now non-virtual
{
doDraw(color); // calls a virtual
}
...
private:
virtual void doDraw(ShapeColor color) const = ; // the actual work is
}; // done in this func
class Rectangle: public Shape {
public:
...
private:
virtual void doDraw(ShapeColor color) const; // note lack of a
... // default param val.
};
Because non-virtual functions should never be overridden by derived classes (see Item 36), this design makes clear that the default value for draw's color parameter should always be Red.
8、Things to Remember
Never redefine an inherited default parameter value, because default parameter values are statically bound, while virtual functions — the only functions you should be overriding — are dynamically bound.
<Item 38> Model "has-a" or "is-implemented-in-terms-of" through composition
9、Composition is the relationship between types that arises when objects of one type contain objects of another type. Among programmers, the term composition has lots of synonyms. It's also known as layering, containment, aggregation, and embedding.
10、Item 32 explains that public inheritance means "is-a." Composition has a meaning, too. Actually, it has two meanings. Composition means either "has-a" or "is-implemented-in-terms-of." That's because you are dealing with two different domains in your software. Some objects in your programs correspond to things in the world you are modeling, e.g., people, vehicles, video frames, etc. Such objects are part of the application domain. Other objects are purely implementation artifacts, e.g., buffers, mutexes, search trees, etc. These kinds of objects correspond to your software's implementation domain. When composition occurs between objects in the application domain, it expresses a has-a relationship. When it occurs in the implementation domain, it expresses an is-implemented-in-terms-of relationship.
12、set implementations typically incur an overhead of three pointers per element. This is because sets are usually implemented as balanced search trees, something that allows them to guarantee logarithmic-time lookups, insertions, and erasures. When speed is more important than space, this is a reasonable design, but it turns out that for your application, space is more important than speed. The standard library's set thus offers the wrong trade-off for you. It seems you'll need to write your own template after all.
13、Things to Remember
Composition has meanings completely different from that of public inheritance.
In the application domain, composition means has-a. In the implementation domain, it means is-implemented-in-terms-of.
<Item 39> Use private inheritance judiciously
14、Item 32 demonstrates that C++ treats public inheritance as an is-a relationship. It does this by showing that compilers, when given a hierarchy in which a class Student publicly inherits from a class Person, implicitly convert Students to Persons when that is necessary for a function call to succeed. It's worth repeating a portion of that example using private inheritance instead of public inheritance:
class Person { ... };
class Student: private Person { ... }; // inheritance is now private 私有继承的时候private可以省略
void eat(const Person& p); // anyone can eat
void study(const Student& s); // only students study
Person p; // p is a Person
Student s; // s is a Student
eat(p); // fine, p is a Person
eat(s); // error! a Student isn't a Person
15、 the first rule governing private inheritance you've just seen in action: in contrast to public inheritance, compilers will generally not convert a derived class object (such as Student) into a base class object (such as Person) if the inheritance relationship between the classes is private. That's why the call to eat fails for the object s. The second rule is that members inherited from a private base class become private members of the derived class, even if they were protected or public in the base class.
16、So much for behavior. That brings us to meaning. Private inheritance means is-implemented-in-terms-of. If you make a class D privately inherit from a class B, you do so because you are interested in taking advantage of some of the features available in class B, not because there is any conceptual relationship between objects of types B and D. As such, private inheritance is purely an implementation technique. (That's why everything you inherit from a private base class becomes private in your class: it's all just implementation detail.) Using the terms introduced in Item 34, private inheritance means that implementation only should be inherited; interface should be ignored. If D privately inherits from B, it means that D objects are implemented in terms of B objects, nothing more. Private inheritance means nothing during software design, only during software implementation.
17、The fact that private inheritance means is-implemented-in-terms-of is a little disturbing, because Item 38 points out that composition can mean the same thing. How are you supposed to choose between them? The answer is simple: use composition whenever you can, and use private inheritance whenever you must. When must you? Primarily when protected members and/or virtual functions enter the picture, though there's also an edge case where space concerns can tip the scales toward private inheritance. We'll worry about the edge case later. After all, it's an edge case.
18、定时器是私有继承的典型应用,在各种钩子和回调函数中也经常使用私有继承
class Timer {
public:
explicit Timer(int tickFrequency);
virtual void onTick() const; // automatically called for each tick
...
};
class Widget: private Timer {
private:
virtual void onTick() const; // look at Widget usage data, etc.
...
};
也可以使用显示的组合来实现
class Widget {
private:
class WidgetTimer: public Timer {
public:
virtual void onTick() const;
...
};
WidgetTimer timer;
...
};
组合相比私有继承有两个好处First, you might want to design Widget to allow for derived classes, but you might also want to prevent derived classes from redefining onTick. If Widget inherits from Timer, that's not possible, not even if the inheritance is private. (Recall from Item 35 that derived classes may redefine virtual functions even if they are not permitted to call them.) .Second, you might want to minimize Widget's compilation dependencies.可以通过pimp的形式减少编译依赖
19、I remarked earlier that private inheritance is useful primarily when a would-be derived class wants access to the protected parts of a would-be base class or would like to redefine one or more of its virtual functions, but the conceptual relationship between the classes is is-implemented-in-terms-of instead of is-a. However, I also said that there was an edge case involving space optimization that could nudge you to prefer private inheritance over composition.
class Empty {}; // has no data( no non-static data members;
no virtual functions (because the existence of such functions adds a vptr to each object — see Item 7);
and no virtual base classes (because such base classes also incur a size overhead — see Item 40). )
// so objects should use no memory
class HoldsAnInt { // should need only space for an int
private:
int x;
Empty e; // should require no memory
};
you'll find that sizeof(HoldsAnInt) > sizeof(int); an Empty data member requires memory. With most compilers, sizeof(Empty) is 1, because C++'s edict against zero-size freestanding objects is typically satisfied by the silent insertion of a char into "empty" objects. However, alignment requirements (see Item 50) may cause compilers to add padding to classes like HoldsAnInt, so it's likely that HoldsAnInt objects wouldn't gain just the size of a char, they would actually enlarge enough to hold a second int. (On all the compilers I tested, that's exactly what happened.)
class HoldsAnInt: private Empty {
private:
int x;
};
you're almost sure to find that sizeof(HoldsAnInt) == sizeof(int). This is known as the empty base optimization (EBO), and it's implemented by all the compilers I tested. If you're a library developer whose clients care about space, the EBO is worth knowing about. Also worth knowing is that the EBO is generally viable only under single inheritance. The rules governing C++ object layout generally mean that the EBO can't be applied to derived classes that have more than one base.
20、In practice, "empty" classes aren't truly empty. Though they never have non-static data members, they often contain typedefs, enums, static data members, or non-virtual functions. The STL has many technically empty classes that contain useful members (usually typedefs), including the base classes unary_function and binary_function, from which classes for user-defined function objects typically inherit. Thanks to widespread implementation of the EBO, such inheritance rarely increases the size of the inheriting classes.
21、Most classes aren't empty, so the EBO is rarely a legitimate justification for private inheritance. Furthermore, most inheritance corresponds to is-a, and that's a job for public inheritance, not private. Both composition and private inheritance mean is-implemented-in-terms-of, but composition is easier to understand, so you should use it whenever you can.
22、Private inheritance is most likely to be a legitimate design strategy when you're dealing with two classes not related by is-a where one either needs access to the protected members of another or needs to redefine one or more of its virtual functions. Even in that case, we've seen that a mixture of public inheritance and containment can often yield the behavior you want, albeit with greater design complexity. Using private inheritance judiciously means employing it when, having considered all the alternatives, it's the best way to express the relationship between two classes in your software.
23、Things to Remember
Private inheritance means is-implemented-in-terms of. It's usually inferior to composition, but it makes sense when a derived class needs access to protected base class members or needs to redefine inherited virtual functions.
Unlike composition, private inheritance can enable the empty base optimization. This can be important for library developers who strive to minimize object sizes.
<Item 40> Use multiple inheritance judiciously
24、before seeing whether a function is accessible, C++ first identifies the function that's the best match for the call. It checks accessibility only after finding the best-match function. In this case, both checkOuts are equally good matches, so there's no best match. The accessibility of ElectronicGadget::checkOut is therefore never examined.
25、To resolve the ambiguity, you must specify which base class's function to call:
mp.BorrowableItem::checkOut(); // ah, that checkOut...
26、Multiple inheritance just means inheriting from more than one base class, but it is not uncommon for MI to be found in hierarchies that have higher-level base classes, too. That can lead to what is sometimes known as the "deadly MI diamond"
27、C++ takes no position on this debate. It happily supports both options, though its default is to perform the replication. If that's not what you want, you must make the class with the data (i.e., File) a virtual base class. To do that, you have all classes that immediately inherit from it use virtual inheritance:
class File { ... };
class InputFile: virtual public File { ... };
class OutputFile: virtual public File { ... };
class IOFile: public InputFile,
public OutputFile
{ ... };
The standard C++ library contains an MI hierarchy just like this one, except the classes are class templates, and the names are basic_ios, basic_istream, basic_ostream, and basic_iostream instead of File, InputFile, OutputFile, and IOFile.
28、From the viewpoint of correct behavior, public inheritance should always be virtual. If that were the only point of view, the rule would be simple: anytime you use public inheritance, use virtual public inheritance. Alas, correctness is not the only perspective. Avoiding the replication of inherited fields requires some behind-the-scenes legerdemain on the part of compilers, and the result is that objects created from classes using virtual inheritance are generally larger than they would be without virtual inheritance. Access to data members in virtual base classes is also slower than to those in non-virtual base classes. The details vary from compiler to compiler, but the basic thrust is clear: virtual inheritance costs.
It costs in other ways, too. The rules governing the initialization of virtual base classes are more complicated and less intuitive than are those for non-virtual bases. The responsibility for initializing a virtual base is borne by the most derived class in the hierarchy. Implications of this rule include (1) classes derived from virtual bases that require initialization must be aware of their virtual bases, no matter how far distant the bases are, and (2) when a new derived class is added to the hierarchy, it must assume initialization responsibilities for its virtual bases (both direct and indirect).
My advice on virtual base classes (i.e., on virtual inheritance) is simple. First, don't use virtual bases unless you need to. By default, use non-virtual inheritance. Second, if you must use virtual base classes, try to avoid putting data in them. That way you won't have to worry about oddities in the initialization (and, as it turns out, assignment) rules for such classes. It's worth noting that Interfaces in Java and .NET, which are in many ways comparable to virtual base classes in C++, are not allowed to contain any data.
虚基类最好设计成纯虚的接口类
29、This leads to one reasonable application of multiple inheritance: combine public inheritance of an interface with private inheritance of an implementation
30、Things to Remember
Multiple inheritance is more complex than single inheritance. It can lead to new ambiguity issues and to the need for virtual inheritance.
Virtual inheritance imposes costs in size, speed, and complexity of initialization and assignment. It's most practical when virtual base classes have no data.
Multiple inheritance does have legitimate uses. One scenario involves combining public inheritance from an Interface class with private inheritance from a class that helps with implementation.
<Effective C++>读书摘要--Inheritance and Object-Oriented Design<二>的更多相关文章
- <Effective C++>读书摘要--Inheritance and Object-Oriented Design<一>
1.Furthermore, I explain what the different features in C++ really mean — what you are really expres ...
- What is Object Oriented Design? (OOD)
Object Oriented Design is the concept that forces programmers to plan out their code in order to hav ...
- <Effective C++>读书摘要--Implementations<二>
<Item29> Strive for exception-safe code. 1.如下面的代码 class PrettyMenu { public: ... void changeBa ...
- <Effective C++>读书摘要--Implementations<一>
1.For the most part, coming up with appropriate definitions for your classes (and class templates) a ...
- <Effective C++>读书摘要--Designs and Declarations<一>
<Item 18> Make interfaces easy to use correctly and hard to use incorrectly 1.That being the c ...
- <Effective C++>读书摘要--Ctors、Dtors and Assignment Operators<二>
<Item 9> Never call virtual functions during construction or destruction 1.you shouldn't call ...
- <Effective C++>读书摘要--Ctors、Dtors and Assignment Operators<一>
<Item 5> Know what functions C++ silently writes and calls 1.If you don't declare them yoursel ...
- <Effective C++>读书摘要--Accustoming Youself to C++
<Item 1>View C++ as a federation of languages. 1.把C++看成4种子语言组成,即C.Object-Oriented C++.Template ...
- <Effective C++>读书摘要--Introduction
Introduction 1.Learning the fundamentals of a programming language is one thing; learning how to des ...
随机推荐
- ckeditor + ckfinder + oss存储
ckeditor 与 ckfinder 的整合方法 网上有很多,这里我也就不说了. (主要是以前整合的现在忘记咋弄的了0.0) 我这里整合后直接使用js代码 <script type=&quo ...
- 最新版的stm32f1xx.h文件中取消了u8, u16, u32的类型定义
使用芯片stm32f103zet6和stm32l151c8t6,在移植程序时发现,编译器提示u8未定义: 在Keil MDK 开发环境里,st定义无符号32位整形数据有很多种表示方法: 1 unsig ...
- python之内存与编码的那点事
一.初始编码 ASCII 码不支持中文 是py2版本中的默认编码 Unicode 万国码, 英文使用16位(即两个字节),中文使用32位(四个字节) utf-8 美国最少使用八位(1字节), 欧 ...
- flask(列表数据接口设计)
新闻列表数据只是当前页面的一部分 点击分类时需要去获取当前分类下的新闻数据 并在展示的时候需要更新新闻列表界面,不需要整体页面刷新 所以新闻数据也使用 ajax 的方式去请求后台接口进行获取 接口设计 ...
- 《TCP/IP详解 卷1:协议》系列分享专栏
<TCP/IP详解卷1:协议>是一本详细的TCP/IP协议指南,计算机网络历久不衰的经典著作之一. 作者理论联系实际,使读者可以轻松掌握TCP/IP的知识.阅读对象为计算机专业学生.教师以 ...
- Java基础之final和abstract关键字
final final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.一旦你将引用声明作final,你将不能改变这个引用了,编译器会检查代码,如果你试图将变量再次初始化的话,编 ...
- java基础之while语句的练习
计算1-100,7的倍数的数的总和 代码如下: public class Demo1 { public static void main(String[] args) { int num=1; int ...
- HTML基础part2
表单标签form 功能:表单用于向服务器传输数据,从而实现用户与Web服务器的交互 表单能够包含input系列标签,比如文本字段.复选框.单选框.提交按钮等等. 表单还可以包含textarea.sel ...
- WebRTC中Android Demo中的摄像头从采集到预览流程
APPRTC-Demo调用流程 1.CallActivity#onCreate 执行startCall开始连接或创建房间 2.WebSocketClient#connectToRoom 请求一次服务器 ...
- c/c++链表的实现
#include<iostream> #include<string> #define SIZE 3 using std::cout; using std::endl; usi ...