Reading Notes Objective c ++ Item 32 ensure that the "is-a" model is created based on the public inheritance, and the specified tiveis-
1. What is the "is-a" relation inherited by public?
The most important criterion in the C ++ object-oriented principle is that public inheritance means "is-". Remember this rule.
If you implement a Class D (derived) public that inherits from Class B (base), you are telling the c ++ Compiler (also telling the code reader ), the object of each type D is also an object of type B, which is not correct. You are telling us that B represents a more general concept than D, and D represents a more special concept than B. You are advocating that type D can also be used in any place where type B can be used, because the object of each type D is an object of type B. In turn, it is not true, that is, where type D can be used, B: D is B, and B is not D.
C ++ enforces this interpretation for public inheritance. See the following example:
1 class Person { ... };2 class Student: public Person { ... };
In our daily life, we know that every student is a person, but not everyone is a student. This is exactly what the above inheritance system claims. We expect everything to be true to people-for example, a person has a date of birth-and it is true to students. We do not expect anything to be true to students-for example, registering for admission to a specific school-to be true to the general public. The concept of people is more general than that of students, while students are a specific type of people.
In the C ++ field, any function that requires the Person type (or pointer to Person or reference to Person) parameter can also use the Student parameter (or pointer or reference ):
1 void eat(const Person& p); // anyone can eat 2 3 void study(const Student& s); // only students study 4 5 Person p; // p is a Person 6 7 8 9 Student s; // s is a Student10 11 eat(p); // fine, p is a Person12 13 eat(s); // fine, s is a Student,14 // and a Student is-a Person15 16 study(s); // fine17 18 study(p); // error! p isn’t a Student
This is only valid for public inheritance. The behavior of C ++ is shown in the preceding description only when Student inherits from Person. The significance of Private inheritance has completely changed (Item 39). protected inheritance is something that has left me confused so far.
2. Public inheritance may mislead you-Example 1: penguins will not fly
Public inheritance is equivalent to "is-a". It sounds simple, but sometimes your instincts mislead you. For example, it is a fact that penguins are birds and birds can fly. If you try to use C ++, the following code will be generated:
1 class Bird { 2 public: 3 virtual void fly(); // birds can fly 4 5 ... 6 7 }; 8 class Penguin: public Bird { // penguins are birds 9 10 ... 11 12 13 };
We suddenly got into trouble because this inheritance system showed that penguins could fly and we knew it was not true. What happened?
2.1 Method 1 for solving the above problems-more precise modeling without defining fly
In this case, we are the victim of an inaccurate language-English. When we say that birds can fly, we do not say that all birds can fly. Generally, this is the only thing we can do. If it is more accurate, we can identify some types of birds that cannot fly, we can use the following inheritance system, which better simulates the reality:
1 class Bird { 2 ... // no fly function is declared 3 4 }; 5 class FlyingBird: public Bird { 6 public: 7 virtual void fly(); 8 ... 9 };10 class Penguin: public Bird {11 ... // no fly function is declared12 13 14 };
This inheritance system is more loyal to reality than the original design.
The issue about poultry is not complete, because for some software systems, there is no need to distinguish between birds that can fly and cannot fly. If your application pays more attention to the wings of the bird's mouth and birds and is not indifferent to flying, the inheritance system of the first two classes will be enough. This reflects a simple fact:No ideal design for all software.The best design depends on what the system needs to do, whether it is the present or the future.. If your application does not have the knowledge related to flying and never does, it may be a perfect and effective design decision to distinguish whether or not to fly. In fact, the design that can differentiate them may be more desirable, because the distinction that you try to model for it may one day disappear from the world.
2.2 Method 2 for solving the above problems-a runtime error is generated
There is another school of thought to deal with the "All birds can fly, penguins are birds, and penguins cannot fly" I described above. It is to re-define the fly function for Penguin, but let it generate a runtime error:
1 void error(const std::string& msg); // defined elsewhere2 class Penguin: public Bird {3 public:4 virtual void fly() { error("Attempt to make a penguin fly!"); }5 ...6 };7 8
What we mentioned above may be different from what you think. It is important to identify them. The above Code does not say, "Penguins cannot fly ." "Penguins can fly, but it would be a mistake if they try to do so ".
2.3 differentiate between the two -- compilation errors and runtime errors
How can you tell their differences? From the time point when the error was detected, the "Penguin cannot fly" ban can be enforced by the compiler, however, if you violate the "it is a mistake for penguins to try to fly" rule, it can only be detected at runtime.
To indicate that the "Penguin cannot fly" restriction, make sure that no such function is defined for the Penguin object:
1 class Bird {2 ... // no fly function is declared3 4 };5 class Penguin: public Bird {6 ... // no fly function is declared7 8 9 };
If you try to let penguins fly, the compiler will condemn your behavior:
1 Penguin p;2 3 p.fly(); // error!4 5
This is very different from the method for generating runtime errors. If you use the method in which an error is reported during running, the compiler will not say a word to p. fly's call. Item 18 explains that a good interface should be able to block Invalid code during compilation, so it is better to detect errors than it can only be detected at runtime, you should be more fond of rejecting penguin flying design during compilation.
3. Public inheritance may mislead you-Example 2: rectangle and square
Maybe you will make concessions because you lack the knowledge of birds, but you can rely on your mastery of preliminary ry, right? How complicated is a rectangle and a square?
Now I want to answer this simple question: should the square class inherit from the rectangle class in public?
You will say, "Of course! Everyone knows that a square is a rectangle, but the opposite is not true ." I guess it's no better, at least in school. But I think we are no longer in school.
Consider the following code:
1 class Rectangle { 2 public: 3 virtual void setHeight(int newHeight); 4 virtual void setWidth(int newWidth); 5 6 virtual int height() const; // return current values 7 8 virtual int width() const; 9 10 ... 11 12 }; 13 14 15 16 void makeBigger(Rectangle& r) // function to increase r’s area17 18 { 19 20 int oldHeight = r.height(); 21 22 23 24 r.setWidth(r.width() + 10); // add 10 to r’s width25 26 assert(r.height() == oldHeight); // assert that r’s27 28 29 30 } // height is unchanged
It is clear that assertions will never go wrong, and makeBigger will only modify the r width. The height will never be modified.
Now consider the following code. Using public inheritance, you can treat a square as a rectangle:
1 class Square: public Rectangle { ... }; 2 3 Square s; 4 5 ... 6 7 assert(s.width() == s.height()); 8 9 // this must be true for all squares10 11 makeBigger(s);12 13 // by inheritance, s is-a Rectangle,14 15 16 // so we can increase its area17 assert(s.width() == s.height()); // this must still be true18 // for all squares
It is clear that the second asserted will never fail. According to the definition, the width and height of a square should be the same.
But now we have a problem. How can we make the following assertions consistent?
- Before makeBigger is called, the s height and width are the same;
- In makeBigger, the width of s is changed, but the height is not;
- After makeBigger returns, the height and width of s are still the same. (Note that s is passed to makeBigger by reference. Therefore, makeBigger modifies s instead of copying s)
Welcome to the wonderful world of public inheritance. Your intuition (including mathematics) learned in other fields may be different in use than you want. The basic difficulty in the above example is that it is applicable to the rectangle (the width is modified independently of the height) but not to the square (the length and width must be the same ). However, the concept of public inheritance applies to anything of the base class object as well as the derived class object. This claim is no longer applicable to rectangle and square cases (as well as the sets and lists examples involved in Item38), so it is incorrect to use public inheritance to model it. The compiler may let you do this, but as we just saw, we cannot ensure that the Code behavior is correct. This is what every programmer must learn: Coding compilation does not mean it can work.
4. New insights are required for using public inheritance.
When using object-oriented design over the years, the intuition on the software will make you fail and don't get bored. This knowledge is still valuable, and now you have added alternative inheritance in your design factory. You must use new insights to expand your intuition, guide you to use inheritance as appropriate. When someone shows you several pages of functions, you will think of the interesting things that penguins inherit from birds or squares inherit from rectangles. It may be the correct way to handle things, but it is not special.
5. other two types of relationships
The "is-a" link is not the only link between classes. The relationships between the other two common classes are "has-a" and "is-implemented-in-terms-". These relationships are described in Item38 and Item39. C ++ design errors are not uncommon, because other important class relationships may be incorrectly modeled as "is-", so you should be sure to understand the differences between these relationships and know how to shape them best in C ++.
6. Summary
Public inheritance means "is-a". Everything that is applied to the base class must also be applied to the derived class, because each derived class object is a base class object.