2. Miscellaneous
We now go to the part of the end, this chapter tells some guidelines that are not part of the previous section. Starting two about C software development, describing systems with design adaptation changes. One power-oriented one is to support changes, which describes the specific steps to enhance your software to change resistance.
Then we analyze how to make C and C mixed programming in the same program. This inevitably leads to problems outside the linguistics, but C exists in the real world, so sometimes we must face this matter.
Finally, I will overview the change in C language standard in "The Annotated C Reference Manual". In particular, I will cover the majority of changes in the standard runtime (see Item E49). If you don't keep up with the standardization process, you will be very surprised - a lot of changes are very happy.
2.1 Item M32: Developer under the future
Things are changing.
As a software developer, we may know almost, but we know that all things will change. We don't have to know what will change, how happened so, when you happen, where we happen, but we know: Everything will change.
Good software can adapt to changes. It provides new features that adapt to new platforms to meet new needs and process new inputs. The software is flexible, robust, and reliability is not from accidents. It is programmers who meet the current needs and pay attention to future possible post-design and implementation. Such software (receiving small changes) is those written in the development program in the future.
To develop programs in future time, you must accept things to change and prepare for this. This is what should be considered: the new function will be added to the library, and the new overload will happen, so pay attention to the result of those vague functions; new classes will join inheritance level, and now the derived class will Will be the base class and have been prepared for this; the new application software will be prepared, the function will be called in a new operating environment, and they should be written on the new platform; the program maintenance personnel Usually people who are not originally prepared, so they should be designed to be understood, maintained and expanded by others.
One way to do this is to express your design constraints in the C language instead of using a comment or document. For example, if a class is designed to be inherited, do not just add a comment in its header file, to prevent inheritance with C ; Item M26 shows this skill. If a class needs to be created in a heap, don't just say this, ITEM M27 method forced this. These operations are prevented if the copy constructor is meaningless to a class (see Item E27) by stating them. C provides powerful features, flexibility and expression. These features provided in words are forced to comply with the design.
Because all things will change, it is necessary to write classes that can withstand confusion attacks in the process of software development. Avoid "Demand-Paged" (WQ: "User Required" The meaning of the meaning of the "user-required", why you have not written virtual functions until someone comes to anyone? It should be judged whether a function is meaningful, and if it is defined by the derived class. If it is meaningful, the declaration it is virtual, even if no one will immediately redefine it. If not, it is not true, and don't make changes to someone in the future; make sure the changes are abstraction indicated by the running environment and classes of the entire class (see Item E36). Handle the assignment and copy constructor of each class, even if "from no one is doing so". They don't have this now don't mean that they don't do this later (see Item E18). If these functions are difficult to implement, then they are applying for them. In this way, there will be no error in the default version provided by the compiler (this often occurs on the default assignment and copy constructor, see Item E11).
Based on minimal surprise, it is struggling to provide such a class, their operations and functions have natural grammar and intuitive semantics. And the behavior of the built-in data type is consistent: When it is not ideal, it is done according to Int.
To admit: As long as you can be done, someone will do this (WQ: Mo Fi law). They will throw their usual; they will assign themselves to themselves; use objects before they are not assigned; they are not used to assign values to objects; they will have large values, too small or null values. In general, as long as it can be compiled, some people will do this. So, to make your own class easily used to be used correctly and it is difficult to misuse. To admit that users may make mistakes, they should be designed to prevent, detect or correct these errors (examples of Item M33 and Item E46).
Work hard to portable code. The portable code is not much more difficult than the non-portable code, and only the non-portable structure is desirable (see Item M16) when performance is extremely important. Even programs for specific hardware design are often transplanted because these platforms have a magnitude performance improvement. Portable code makes you easier to replace the platform, expand your user base, boasting the open platform. This also makes you more easy to remedy when you have a mistake.
Designed your code when you need to change, the impact is partial. Package as much as possible; the details will be declared as private (see Item E20). As long as it is possible, use a static object or function within the file (see Item E31). Avoiding the design of the virtual base class, because this class requires each derived class directly to initialize it - even those indirectly generate (see Item M4 and Item E43). Avoiding the need for RTTI design, it requires if ... Then ... Else type waterfall structure (see Item M31 again, then see the good way to Item E39). Each time, the inheritance level of the class has changed, each group of if ... Then ... Else statement needs to be updated, if you forget one, you will not get any alarm from the compiler.
This is a famous old man who is often talking, but most of the programmers still violate it. Look at this famous C expert proposes advice (very unfortunate, many authors say this):
You need a false prefix function, as long as someone delete an actual value to D's b *.
Here, B is the base class, and D is its assignment. In other words, this author hints if your program looks like this, does not need to have a false arrangement function: Class B {...}; // no virtual dtor needed
Class D: public b {...};
B * PB = New D;
However, when you join this sentence, the situation has changed:
DELETE PB; // Now you need the Virtual
// deStructor In B
This means that a small change in the user code - adds a delete statement - actually leads to the definition of B. If this happens, all B users must recompile. If this authors are admitted, the increase in statement will result in a large number of code reconstruction and weight. This is by no means an efficient design.
On the same topic, another author wrote:
If a public base class does not have a false aromal function, all derived class based behalf of its member function should have a destructive function.
In other words, this is no problem:
Class string {// from the standard C Library
PUBLIC:
~ String ();
}
Class B {...}; // no data members with dtors,
// NO Virtual Dtor Needed
But after inheriting a new class from B, things have changed:
Class D: public b {
String name; // now ~ b Needs to be virtual
}
Once again, a small change of the use of B (here is a derived class that adds a member object containing the designer function) may require a large number of code reconstruction and linked. But in the system, small changes should only have a small impact. This design failed this test.
The same author wrote:
If multiple inheritance systems have many parses, each base class should have a false argument function.
All of these references are concerned about timing considerations. How do users now operate? What member current class has a destructor? What kind of patronization function is in the inheritance system?
Future Times is completely different. Not asking a class is now useful, but asking this class is designed to use it. In the future, considerations are considered: If a class is designed to make a base class (even if it is not used now), it should have a false argument function (see Item E14). Such classes are correct in the present and future, and when new categories are derived from them, they do not affect other library users. (At least, they have no effect until their destructor is used. If you need additional modification of the class, other users will be affected.)
There is a business class library (before the C standard Runturship Stirbed String) contains a String class with no false argument function. Its manufacturer explains:
We didn't use the false preframework, because we don't want this String class with VTBL. We don't even expect to have a string *, so this is not a problem. We are very clear that this will be difficult.
Is this a tense consideration or future time considering?
Of course, VBTL has a technical problem (see Item M24 and Item E14). The implementation of most String classes is only one char * pointer inside the object, so adding a VPTR to double the size of each String class. So it is understood why it is refused to achieve it, especially for String that frequently uses high-density use. Such classes are part of the 20% of the influence program performance (see Item M16). Also, a full memory of a String object - it adds yourself to the string in the heap - usually greater than the size of the CHAR * pointer. In this respect, it is not so important for the increase in VPTR. However, this is still a legal design. (Indeed, ISO / ANSI Standards Committee seems to be so thinking: The standard string type has a non-virtual destructor.)
More problematic is the annotation of the manufacturer: "We don't even expect to have a string *, so this is not a problem." This may be correct, but their String class is part of the class library that provides thousands of developers. There are so many developers, each person is different from C , and the method of everyone doing things is different. These people understand the consequences of String no false argument function? The manufacturer is confident that their customers know that there is no false argument function, and use the string * pointer delete object, which may be incorrect in String's pointer and reference to the reference, can I get incorrect information? This class is easy to use correctly and is not easy to use wrong?
This producer should provide a clear document to point out that his String class is not designed to be inherited, but what happens if the programmer does not pay attention to this warning or fail to read this document?
An optional method is to prevent inheritance with C himself. The Item M26 describes how to limit the object only in a heap and use the Auto_Ptr object to operate the object in the heap. The interface that constructs the String object will not meet the tradition is not convenient, it is necessary to:
Auto_PTR
... // Treat Ps as a Pointer To
// a string object, but don't
// WORRY ABOUT DELETING IT
Come instead of this:
String S ("Future Tense C );
But, mostty, in order to reduce incorrect inheritance behavior, it is worthwhile to convert. (For the String class, this is not necessarily cost, but for other classes, such exchanges are completely worthwhile.)
Of course, it is also necessary to conduct a timing consideration. The software you develop must work under the current compiler; you can't wait until the latest language characteristics (compiled) are implemented. It must work on the hardware that is now supported, and you must work in your user's existing (software); you can't force customer upgrade system or change the operating environment. It must now provide acceptable performance; promise a smaller and faster programs after a few years, it is completely unable to attract potential users. The software you have participated must be "as soon as possible", usually it means that it has been misunderstood (Which offen means Some Time In the Recent Past). These are important constraints. You can't ignore them.
The future consideration is just simply adding some extra constraints:
* Provide a complete class (see Item E18), even if some parts are not used yet. If there is a new demand, you don't have to go back to change them. * Designed your interface to facilitate common operations and prevent common errors (see Item E46). Make the class easy to use correctly without easy to use the wrong. For example, prevent copy constructing and assignment operations, if they don't make sense to this class (see Item E27). Prevent partial assignment (see Item M33).
* If there is no restrictions you cannot universalize your code, then it is universally. For example, if it is a traversal algorithm for writing a tree, consider genericity that can be processed any directionless.
In the future, considering the reusability of your code, maintainability, robustness, and easy to modify when there is a change in the environment. It must be taken with the constraints of the timed. Too many programmers are only concerned about the current needs, but they have sacrificed the long-term survivability of their software. It is different, and it is a traitor, and the program is developed in the future.
2.2 Item M33: Designed from non-tail class as abstract class
Suppose you are working in a software project, it handles animals. In this software, most animals can be abstract, but two animals-clarity and chicks - need special treatment. Obviously, the link between the clarity and the chick and the animal is like this:
Animal
| | |
/ /
/ /
/ /
Lizard Chicken
Animal class treats all animals common characteristics, clarity and chickens specialized animals to suit these two animals.
This is their simplified definition:
Class Animal {
PUBLIC:
Animal & Operator = (Const Animal & RHS);
...
}
Class Lizard: Public Animal {
PUBLIC:
Lizard & Operator = (Const Lizard & RHS);
...
}
Class chicken: public animal {
PUBLIC:
Chicken & Operator = (Const Chicken & RHS);
...
}
Here only the assignment function is written, but it is enough for us to be busy. Look at this code:
Lizard Liz1;
Lizard Liz2;
Animal * PANIMAL1 = & liz1;
Animal * PANIMAL2 = & liz2;
...
* PANIMAL1 = * PANIMAL2;
There are two problems here. First, the final row assignment is called the Animal class, although the type of related object is lizard. As a result, only the Animal section of LIZ1 is modified. This is partial assignment. After assignment, Liz1's Animal has a value from Liz2, but its Lizard member part has not been changed.
The second question is really some programmers write the code. It is a lot of programs that are assigned to objects with pointers, especially those who have extensive experience to C . Therefore, we should design more reasonable. As Item M32 pointed out, our class should be easily applicable without being used wrong, and the above level is easy to use.
A solution is to declare the assignment operation as a virtual function. If Animal :: Operator = is a virtual function, the assignment statement will call the value of lizard (the version that should be called). However, let's take a look at what it will happen to the virtual future:
Class Animal {
PUBLIC:
Virtual Animal & Operator = (Const Animal & RHS); ...
}
Class Lizard: Public Animal {
PUBLIC:
Virtual Lizard & Operator = (Const Animal & RHS);
...
}
Class chicken: public animal {
PUBLIC:
Virtual Chicken & Operator = (Const Animal & RHS);
...
}
Based on the revised modification based on the C language, we can modify the type of return value (so each of the correct classes), but C rules force us to declare the same parameter type. This means that the assignment of the Lizard class and the Chicken class must be prepared to accept any type of Animal object. That is, this means that we must face this fact: the following code is legal:
Lizard Liz;
Chicken chick;
Animal * PANIMAL1 = & liz;
Animal * PANIMAL2 = & cho;
...
* PANIMAL1 = * PANIMAL2; // Assign A Chicken TO
// a lizard!
This is a mixed type assignment: the left is a lizard, and the right is a Chicken. Mixed type assignment is usually not a problem in C , because the strong type of C will be illegally illegal. However, by setting animal assignment operation to a virtual function, we open the door of the mixed type operation.
This makes our situation difficult. We should allow the same type to assign values through the pointer, but it is forbidden to assign a mixed type by the same pointer. In other words, we want to allow this:
Animal * PANIMAL1 = & liz1;
Animal * PANIMAL2 = & liz2;
...
* PANIMAL1 = * PANIMAL2; // Assign a lizard to a lizard
And want to ban this:
Animal * PANIMAL1 = & liz;
Animal * PANIMAL2 = & cho;
...
* PANIMAL1 = * PANIMAL2; // Assign A Chicken to a lizard
You can only distinguish between the running period, because * Panimal2 is assigned * Panimal1 is sometimes correct, sometimes not. We have fallen into the dark world of runtime runtime. In particular, we need to assign a value when mixing type, and the type is incorrectly in Operator =, and the type is the same, we expect to complete the assignment in the usual manner.
We can use Dynamic_CAST (see Item M2) to be implemented. Here's how to implement the assignment of lizard:
Lizard & lizard :: Operator = (Const Animal & RHS)
{
// Make Sure RHS Is Really a Lizard
Const lizard & rhs_liz = Dynamic_cast
Proceed with a normal assocignment of rhs_liz to * this;
}
This function is only given to * this when RHS is indeed a lizard type. If rhs is not a lizard type, the function passes the BAD_CAST type of the Dynamic_cast to fail. (In fact, the type of abnormality is std :: BAD_CAST, because the components of the standard runtime, including the exceptions thrown, are located in the namespace STD. For the outline of the standard runtime, see Item E49 and ITEM M35). Even if there is an abnormality, this function looks unnecessary complex and expensive - Dynamic_Cast is necessary to reference a Type_info structure; see Item M24 - Because it is usually a lizard object to give another:
Lizard Liz1, Liz2;
...
Liz1 = Liz2; // no need to perform a
// Dynamic_cast: this
// Assignment Must Be Valid
We can handle this situation without increasing complexity or spending Dynamic_CAST, as long as a usual formal assignment operation is added to lizard:
Class Lizard: Public Animal {
PUBLIC:
Virtual Lizard & Operator = (Const Animal & RHS);
Lizard & Operator = (const lizard & rhs); // add this
...
}
Lizard Liz1, Liz2;
...
Liz1 = Liz2; // Calls operator = taking
// a const lizard &
Animal * PANIMAL1 = & liz1;
Animal * PANIMAL2 = & liz2;
...
* PANIMAL1 = * PANIMAL2; // Calls operator = TAKING
// a const animal &
In fact, it gives the Operator =, which simplifies the realization of the former:
Lizard & lizard :: Operator = (Const Animal & RHS)
{
Return Operator
}
This function is now trying to convert RHS to a lizard. If the conversion is successful, the usual assignment operation is called; otherwise, a BAD_CAST exception is thrown.
To be honest, use Dynamic_cast to detect in the runtime period, which makes me very nervous. One thing to note, some compilers still do not support Dynamic_cast, so although it is theoretically portable, it is actually not necessarily. More importantly, it requires users who use lizard and chicken must be ready to capture Bad_cast exceptions at each assignment. If they don't do this, then I don't know if we get the benefits of exceeding the initial solution.
It is pointed out that this is a very unsatisfactory state of the virtual value operation, rearculates in the most beginning to try to find a way to prevent the user from writing a problem with the assignment statement is necessary. If this assignment statement is rejected in the compile period, we don't have to worry about doing something wrong.
The easiest way is to set the Operator = Private in Animal. Thus, the Lizard object can assign a value to the lizard object, and the Chicken object can assign a value to the Chicken object, but the part or mixed type assignment is disabled: Class Animal {
Private:
Animal & Operator = (Const Animal & RHS); // this is now
... // Private
}
Class Lizard: Public Animal {
PUBLIC:
Lizard & Operator = (Const Lizard & RHS);
...
}
Class chicken: public animal {
PUBLIC:
Chicken & Operator = (Const Chicken & RHS);
...
}
Lizard Liz1, Liz2;
...
Liz1 = liz2; // fine
Chicken Chick1, Chick2;
...
Chick1 = Chick2; // Also Fine
Animal * PANIMAL1 = & liz1;
Animal * PANIMAL2 = & chick1;
...
* PANIMAL1 = * PANIMAL2; // Error! Attempt to Call
// private animal :: operator =
Unfortunately, Animal is also a physical class. This method simultaneously evaluates the assignment between animal objects as illegal:
Animal Animal1, Animal2;
...
Animal1 = animal2; // error! Attempt to call
// private animal :: operator =
Moreover, it also makes it impossible to correctly implement the assignment operation of the Lizard and Chicken classes, because the assignment of the derived class is responsible for calling the assignment function of its base class:
Lizard & lizard :: Operator = (const lizard & rhs)
{
IF (this == & r Hs) return * this;
Animal :: Operator = (rhs); // error! Attempt to Call
// private function. but
// lizard :: Operator = MUST
// Call this function to
... // Assign THE Animal Parts
} // of * this!
Later, this problem can be resolved by applying animal :: Operator = to protected, but "Allows the assignment between Animal objects to prevent Lizard and Chicken objects from partially assigning partial assignments". " What should I do?
The easiest thing is to rule out the needs of the AnImal object assignment, and the easiest implementation method is to design animal as an abstract class. As an abstract class, Animal cannot be instantiated, so there is no need to assign a value between Animal objects. Of course, this has led to a new issue because our original design indicates that animal object is necessary. There is a very easy solution: don't have to set animal to an abstract class, we create a new class - called Abstractanimal - to include the common attributes of Animal, Lizard, ChikCen, and set it to an abstract class. Each entity class is then inherited from Abstractanimal. The modified inheritance system is like this: Abstractanimal
| | | | |
/ | /
/ | /
/ | /
Lizard Animal Chicken
The definition of the class is:
Class abstractanimal {
protected:
Abstractanimal & Operator = (Const Abstractanimal & RHS);
PUBLIC:
Virtual ~ Abstractanimal () = 0; // See Below
...
}
Class Animal: Public AbstractActanimal {
PUBLIC:
Animal & Operator = (Const Animal & RHS);
...
}
Class Lizard: Public AbstractActanimal {
PUBLIC:
Lizard & Operator = (Const Lizard & RHS);
...
}
Class Chicken: Public AbstractActanimal {
PUBLIC:
Chicken & Operator = (Const Chicken & RHS);
...
}
This design is given to you so what you need. Assignment between the same type is allowed, partial assignment, or different types of assignments are disabled; the assignment operation function of the derived class can invoke the value of the base class. In addition, all code involving an AIAML, Lizard or Chicken class does not need to be modified, because these classes still operate, their behavior is consistent with the introduction of Abstractanimal. Affairs, these codes need to be recompiled, but this is for "ensuring that the behavior of the assignment statement" is correct and the behavior may incorrect assignment statements cannot be compiled through "the small price paid.
To make this, the Abstractanimal class must be an abstract class - it must have at least one pure virtual function. In most cases, it is no problem with such a function, but in the case of very little, you will find that you need to create a class such as Abstractanimal, no member function is a natural pure virtual function. At this point, the traditional method is to declare the destructive function as a pure virtual function; this is also used above. In order to support the polymorphism, the base class generally requires the false prefix function (see Item 14), and the only troublesome to set it to pure virtuality must implement it outside the definition of the class (see P195, ITEM M29).
(If you implement a pure virtual function, you just have a knowledge that you are not well enough. The declaration of a function does not mean that it does not implement it, it means:
* The current class is an abstract class
* Any physical classes from this class must declare this function as a "ordinary" virtual function (that is, can not bring "= 0"), most of the pure virtual functions are not realized, but pure pair Structure function is a special case. They must be implemented because they will also be called when the derived classification function is called. Moreover, they often perform useful tasks, such as release resources (see Item M9) or record messages. Implementing a pure virtual function is generally uncommon, but it is not just common, it is necessary. )
You may have noticed that the problem of assigning through the base class pointer here is based on the assumption that the entity class (such as animal) is a data member. If they do not have a data member, you may point out, then there will be no problem, which is safe from a new entity class from an unfailed entity class.
There are no data categories that can become entity classes without data: in the future, or it may have data members, or it still doesn't. If it may have a data member in the future, you are doing now is delayed (until the data member is added), you have long pain with a shortness (see Item M32). If this base class does not have a data member, then it is now an abstract class, what is the use of entity categories without data?
The physical base class such as Animal is replaced with abstract base classes such as Abstractanimal, which is much easier to easily understand the behavior of Operator =. It also reduces the possibility of trying to use polymorphisms to array, this behavior is unpleasant, interpretation of Item M3. However, this skill is the most designed level, because this replacement force you explicitly recognize an entity of an abstract behavior of a useful place. That is, it makes you create a new abstract class for useful prototypes, even if you don't know the existence of this useful prototype.
If you have two entity classes C1 and C2 and you like C2 public inheritance from C1, you should change the inheritance level of the two classes to the inheritance hierarchy, by creating a new abstract class A and will C1 and C2 From it to:
C1 a
| //
| / /
C2 C1 C2
Your initial idea has changed the inheritance level
The importance of this modification is to force you to determine abstract class A. It is very clear, C1 and C2 have contrast; this is why they use public inheritance (see Item E35). After the modification, you must determine what these copies are. Moreover, you must organize these commonly organized these copies of C , it will no longer be ambiguous, it reaches an abstract type level, which has a clear defined member function and a clear definition.
All this has led to some disturbing thinking. After all, each class has completed certain types of abstraction, should we create two classes in this inheritance system to target each prototype (one is an abstract class to indicate its abstract part (To Embody the Abstract Part of Thae Abstract, one is the physical class to represent the object generating part (to Embody the objection part of the Abstract)? No. If you do this, there will be too many classes in the inheritance system. Such inheritance systems are difficult to understand, it is difficult to maintain, the cost of compilation is expensive. This is not the purpose of object-oriented design.
Its purpose is to confirm useful abstraction and force them (and only them) put them in an entity such as an abstract class. But how do you confirm useful abstraction? Who knows what abstraction is proved in the future? Who can predict what he will inherit from the future? Ok, I don't know how to predict a inheritance system future use, but I know one thing: abstraction in a place may just make a hacker, but many places need abstraction usually make sense. Then, useful abstraction is the abstraction that is needed. That is, they are equivalent to this class: It is useful to themselves (for example, there is such a type of object), and they are also useful for one or more derived classes.
When a prototype is first required, we can't prove the simultaneous creation of an abstract class (for this prototype) and a physical class (for the object corresponding to the prototype) is correct, but the second time, we can do this. is correct. The modifications I have described simply realize this process and forced design and programmers to express those useful abstractions in the process of doing this. Even if they don't know those useful prototypes. This also happens to make the correct assignment behavior.
Let's take a look at a simple example. Suppose you are preparing a program to handle mobile information between the local area online, by disassembling it as a packet and transmits according to some protocol. We believe should be used to represent these data packets, and these packets are the core of the program.
Suppose only one transport protocol you handle, there is only one package. Maybe you have heard of other protocols and packet types, but they have never supported them, nor can they support them in the future. Will you design a abstract class for the packet (for the concert tryet represents, designing a physical class you actually use? If you do this, you can add new packets in the future without changing the base class. This allows you to add new packet types when programs do not need to recompile. But this design requires two classes, and you only need one now (for the special packet type you are using now). Is this worth it, increase the complexity of the design to allow expansion characteristics, and this expansion may never happen?
There is no correct choice, but experience shows that the excellent class is almost impossible for our prototype design. If you design an abstract class for your packet, how do you guarantee it correct, especially in your experience, is only limited to this only packet type? Remember, you can get benefits from the abstract class from your packets only when your design class can be inherited from it from it without any modifications. (If it needs to be modified, you have to recompile all the code that uses the packet class, you have not got any benefits.)
It is not easy to design a receipt of abstract design package, unless you are proficient in the differences between various packets and their corresponding environment. Given your limited experience, I recommend not defining abstract classes, waiting until it will be added from the physical classes.
What I said is a method of judging whether an abstract class is needed, but not only the way. There are still many other good ways; talking about object-oriented books is full of such methods. "When the discovery requirement is derived from one entity class, this is not the only place to introduce an abstract class. Anyway, it is necessary to link two entities through the public inheritance, usually referred to as a new abstract class.
This situation is so common, so it has caused our deep thinking. Third-party C class libraries are more and more, when you find that you need a new entity class from the entity class in the class library, and this library you only have only read rights, what do you do?
You can't modify the class library to join a new abstract class, so your choice will be very limited, very boring: * From the existing entity class, you can have your entity class, and tolerate our assignment at this ITEM. problem. You should also pay attention to the array questions in Item M3.
* Attempting to find an abstract class that completed the most of the features you need, inheriting from it. Of course, there may be no suitable classes; even if you have, you may have to repeat things that have been implemented in the entity class (you try to extension).
* Use the way you try to inherit the class of the class to achieve your new class (see Item E40 and Item E42). In the case of the example, you use the objects of the classes in a class library as a data member and focus on your interface in your class:
Class window {// this is the library class
PUBLIC:
Virtual Void Resize (int newwidth, int newheight);
Virtual void repaint () const;
Int width () const;
INT height () const;
}
Class SpecialWindow {// this is the class you
Public: // wanded to have inherit
... // from window
// Pass-Through Implementations of Nonvirtual Functions
Int width () const {return w.width ();
INT height () const {return w.height ();
// new importations of "inherited" Virtual Functions
Virtual Void Resize (int newwidth, int newheight);
Virtual void repaint () const;
Private:
WINDOW W;
}
This method requires you to update your own class when you upgrade each time the class library. It also needs you to give up the ability to redefine the virtual functions of the classes in the class library, because you are not inherited.
* Use you get it. Use classes in the class library to modify your own program. Use non-member functions to provide extension (those you want to join that class). As a result, the program will not be as clear, efficient, maintained, and scalable, but at least it has completed the features you need.
These choices are not very attractive, so you have to make a judgment and choose the lightest poison. This is not interesting, but life is sometimes the case. Want to make things easier to you (and our others), feedback to the class library manufacturer. Relying on luck (and a large number of user feedback), the design may be improved over time.
Finally, the general rules are: non-end classes should be an abstract class. When processed outside the class library, you may need to violate this rule; but for the code you can control, comply with it can improve the programs, robust, readability, scalability.