More effective tive C ++ reading abstract (6. Miscellaneous) item32-35

Source: Internet
Author: User

Item32. develop programs in future tense:

 

As software developers, we may not know enough, but we know that everything will change. We don't need to know what will happen, how it will happen, when it will happen, and where it will happen, but we know:Things always change.

 

Good software can adapt to changes. It provides new features to adapt to new platforms, meet new requirements, and process new inputs. The flexibility, robustness, and reliability of such software do not come from luck. It is designed and implemented by developers who have followed the current requirements and have followed the future. The software that should be changed elegantly is written by those who develop programs in the future.

 

The new function will be added to the function library, and the new overload will occur, so pay attention to the ambiguity of the potential function calls. The new class will be added to the inheritance level, the current derived class will be the base class in the future; a new application software will be compiled, and the function will be called in the new runtime environment, they should be written to run correctly on the new platform. Program maintenance personnel are generally not the people who originally wrote them, so they should be designed to be easily understood, maintained, and expanded by others.

 

A good practice is to use the C ++ language to express design constraints rather than comments or documents.

 

For example, if a class is designed to be unable to be inherited, do not just add a comment in its header file and use the C ++ method to prevent inheritance. Item M26 shows this technique. If a class needs to create all its instances in the heap, do not just say this to the user, use the item M27 method to force this. If the copy constructor and assign values are meaningless to a class, these operations are blocked by declaring them as private (see item e27 ). C ++ provides powerful functions, flexibility, and expressiveness. These features are provided by the language to force the program to conform to the design.

 

Avoid changing the virtual function declaration ("demand-paged") as needed "). Determine the meaning of a function and whether it makes sense if it is redefined by a derived class. If it makes sense, declare it as virtual, even if no one immediately redefines it. If not, declare it to be non-virtual, and do not change it for the convenience of someone in the future; make sure that the changes are meaningful to the runtime environment of the entire class and the abstraction represented by the class (see item E36 ).

 

Process the assignment and copy constructor of each class, even if "never done this ". This does not mean they will not do so in the future (see item E18 ). If these functions are hard to implement, declare them as private. In this way, no one miscalls the default version provided by the compiler and does anything wrong (this often happens in the default value assignment and copy constructor, see item E11 ).

 

Efforts are made to provide such classes. Their operations and functions have natural syntax and intuitive semantics. Consistent with built-in data types:If you have no idea, do it like Int..

 

Remember: as long as it can be done by people, someone will do this (WQ: mofi Law ). They will throw an exception, assign values to themselves, use the object before the initial value is assigned, and assign values to the object instead of using it; a value that is too large, too small, or null is assigned. In general, as long as the compilation is successful, someone will do this. Therefore, it is difficult to misuse your classes if they are used correctly. To acknowledge that users may make mistakes, design your class to prevent, detect, or correct these errors (for example, item M33 and item e46 ).

 

Try to transplant code. Writing portable code is not much more difficult than non-portable code. It is advisable to use a non-portable structure when the performance is extremely important (see item M16 ).

 

Design your Code as follows:When changes are required, the impact is local.. Encapsulate as much as possible; declare implementation details as private (for example, see item E20 ). Use an unnamed namespace and static objects or functions in the file (see item E31) whenever possible ). Avoid leading to the design of the virtual base class, because each derived class needs to initialize it directly-even those indirect Derived classes (see item M4 and item E43 ). To avoid the need for rtti design, it requires the IF... then... else waterfall structure (see item m31 again, and then see the good method on item E39 ). Each time, the hierarchy of classes changes, and the IF... then... else statements in each group need to be updated. If you forget one, you will not receive any warning from the compiler.

 

Of course, the current tense thinking is necessary. The future tense only adds some additional constraints:
* Provides a complete class (see item E18), even if some parts are not used yet. If you have new requirements, you don't have to go back and change them.
* Design your interface to facilitate common operations and prevent common errors (see item e46 ). This makes the class easy to use correctly without errors. For example, block copy constructor and assign values if they do not make sense to this class (see item e27 ). Prevent partial value assignment (see item M33 ).
* If there is no limit that you cannot generalize your code, it will be generalized. For example, if you write a tree traversal algorithm, you can use it to process any directed acyclic graph.

 

Future Tense considerations increase the reusability, maintainability, and robustness of your code, and are easy to modify when the environment changes. It must be used with the constraint condition for temporal. Too many programmers only focus on their current needs, but they sacrifice the long-term viability of software.Be a different person, be a defectTo develop programs in the future tense.

 

Item33. design non-ending classes as abstract classes:

 

Suppose you are writing a software project for processing animals.
This is their simplified definition:

Class animal {<br/> Public: <br/> Animal & operator = (const Animal & RHs); <br/>... <br/>}; <br/> class lizard: Public animal {<br/> Public: <br/> lizard & operator = (const lizard & RHs ); <br/>... <br/>}; <br/> class chicken: Public animal {<br/> Public: <br/> chicken & operator = (const Chicken & RHs ); <br/>... <br/> };

Consider the following assignment operation:
Lizard liz1;
Lizard liz2;
Animal * panimal1 = & liz1;
Animal * panimal = & liz2;
...
* Panimal1 = * panimal;
There are two problems: first, the last line calls the value assignment operator of the animal class, although the object type is lizard. As a result, only the animal part of liz1 is modified. This is partial assignment. After the assignment, the animal member of liz1 has a value from liz2, but the lizard member of liz1 is not changed.

 

The second problem is that some programmers write the code like this. It is not uncommon to assign values to objects using pointers, especially for programmers who have a wealth of experience in C and have transferred to C ++. Therefore, we should make the assignment more reasonable. As pointed out by item M32, our class should be easily correctly applied and not easily used or wrong, and the above class level is easy to be used or wrong.

 

One solution is to declare the value assignment operation as a virtual function. If animal: Operator = is a virtual function, the value assignment statement calls the value assignment operation of lizard (the version to be called ). However, let's see what happens after declaring it as virtual:
Class animal {<br/> Public: <br/> virtual Animal & operator = (const Animal & RHs); <br/>... <br/>}; <br/> class lizard: Public animal {<br/> Public: <br/> virtual lizard & operator = (const Animal & RHs ); <br/>... <br/>}; <br/> class chicken: Public animal {<br/> Public: <br/> virtual chicken & operator = (const Animal & RHs ); <br/>... <br/> };

Based on the recent changes in the C ++ language, the types of returned values between overwritten virtual functions are not required to be completely consistent, however, the C ++ rules force the overwriting virtual functions to declare identical parameter types. This means that the assignment operation of the lizard and Chicken classes must be able to accept any type of animal objects. That is to say, this means we must face the fact that the following code is legal:
Lizard Liz;
Chicken chick;
Animal * panimal1 = & Liz;
Animal * panimal = & chick;
...
* Panimal1 = * panimal; // assign a chicken
// A lizard!
This is a mixed type assignment: lizard on the left and chicken on the right. The mixed type assignment is usually not a problem in C ++, because the strong type test of C ++ determines that they are invalid. However, by setting the value assignment operation of animal to a virtual function, we open the door for mixed type operations.

 

You can only differentiate them at runtime, because assigning * panimal to * panimal1 is sometimes correct, sometimes not.

 

We can also use dynamic_cast (see item m2) to achieve this: when assigning values to the hybrid type, it indicates that an error occurs in operator = while the types are the same, we expect the assignment to be completed in the normal way. The following describes how to assign values to lizard:

Lizard & lizard: Operator = (const Animal & RHs) <br/>{< br/> // make sure RHS is really a lizard <br/> const lizard & rhs_liz = dynamic_cast <const lizard &> (RHs ); <br/> proceed with a normal assignment of rhs_liz to * This; <br/>}

This function is assigned to * this only when RHS is indeed of the lizard type. If the RHS type is not lizard, The bad_cast type exception is thrown when the function fails to pass the dynamic_cast conversion. (In fact, the exception type is STD: bad_cast, because the components of the standard Runtime Library, including the exceptions they throw, are located in the namespace STD. For an overview of the standard Runtime Library, see item e49 and item m35 ).

 

We can also deal with this situation without increasing complexity or consuming dynamic_cast, as long as we add a normal form of value assignment in lizard:

Class lizard: Public animal {<br/> Public: <br/> virtual lizard & operator = (const Animal & RHs ); <br/> lizard & operator = (const lizard & RHs); // Add this <br/>... <br/>}; <br/> lizard liz1, liz2; <br/>... <br/> liz1 = liz2; // calloperator = Taking <br/> // a const lizard & <br/> animal * panimal1 = & liz1; <br/> animal * panimal = & liz2; <br/>... <br/> * panimal1 = * panimal; // calloperator = Taking <br/> // a const Animal &

In fact, the operator = below is given, which simplifies the implementation of the former:

Lizard & lizard: Operator = (const Animal & RHs) <br/>{< br/> return operator = (dynamic_cast <const lizard &> (RHs )); <br/>}

Now this function is trying to convert RHS into a lizard. If the conversion is successful, the usual value assignment operation is called; otherwise, a bad_cast exception is thrown.

But to be honest, I am very nervous when I use dynamic_cast for type detection at runtime.

 

The easiest way is to set operator = to private in animal. Therefore, the lizard object can be assigned to the lizard object, and the chicken object can be assigned to the chicken object, but partial or mixed type assignment is disabled:

Class animal {<br/> PRIVATE: <br/> Animal & operator = (const Animal & RHs); // This is now <br/>... // Private <br/>}; <br/> class lizard: Public animal {<br/> Public: <br/> lizard & operator = (const lizard & RHs ); <br/>... <br/>}; <br/> class chicken: Public animal {<br/> Public: <br/> chicken & operator = (const Chicken & RHs ); <br/>... <br/>}; <br/> lizard liz1, liz2; <br/>... <br/> liz1 = liz2; // fine <br /> Chicken chick1, chick2; <br/>... <br/> chick1 = chick2; // also fine <br/> animal * panimal1 = & liz1; <br/> animal * panimal = & chick1; <br/>... <br/> * panimal1 = * panimal; // error! Attempt to call <br/> // Private animal: Operator =

Unfortunately, animal is also a specific class. This method also determines that the assignment between animal objects is invalid:
Animal animal1, animal;
...
Animal1 = animal; // error! Attempt to call
// Private animal: Operator =
Moreover, it makes it impossible to correctly implement the assignment operations of the lizard and Chicken classes, because the assignment operation functions of the derived classes have the responsibility to call the assignment operation functions of their base classes:

Lizard & lizard: Operator = (const lizard & RHs) <br/>{< br/> If (this = & RHs) return * This; <br/> animal:: Operator = (RHs); // error! Attempt to call <br/> // Private function. but <br/> // lizard: Operator = must <br/> // call this function to <br/>... // assign the animal parts <br/>} // of * This!

This problem can be solved by declaring animal: Operator = as protected, however, there is still a dilemma that "both allow assignment between animal objects and prevent lizard and chicken objects from being partially assigned through the animal Pointer. What should the program do?

 

The easiest thing is to exclude the need to assign values between animal objects. The easiest way to achieve this is to design animal as an abstract class. As an abstract class, animal cannot be instantiated, so there is no need to assign values between animal objects. Of course, this leads to a new problem because our initial design shows that the animal object is required. There is an easy solution: instead of setting animal as an abstract class, we create a new class called abstractanimal to include the common attributes of animal, lizard, and chikcen, and set it as an abstract class. Then, each entity class is inherited from abstractanimal. The modified inheritance system is as follows:

Class extends actanimal {<br/> protected: <br/> extends actanimal & operator = (const extends actanimal & RHs); <br/> Public: <br/> virtual ~ Required actanimal () = 0; // see below <br/>... <br/>}; <br/> class animal: Public extends actanimal {<br/> Public: <br/> Animal & operator = (const Animal & RHs ); <br/>... <br/>}; <br/> class lizard: public role actanimal {<br/> Public: <br/> lizard & operator = (const lizard & RHs ); <br/>... <br/>}; <br/> class chicken: Public login actanimal {<br/> Public: <br/> chicken & operator = (const Chicken & RHs ); <br/>... <br/> };

To make all this work, the abstractanimal class must be an abstract class -- it must have at least one pure virtual function. In rare cases, you will find that you need to create a class such as abstractanimal. No member function can naturally be declared as a pure virtual function. In this case, the traditional method is to declare the Destructor as a pure virtual function. To support polymorphism, the basic class always requires a virtual destructor (see item 14). The only trouble to declare it as pure virtual is that it must be implemented outside the definition of the class.

(Declaring a function as virtual does not mean it is not implemented, it means:
* The current class is an abstract class.
* Any entity class derived from this class must declare this function as a "normal" virtual function (that is, it cannot contain "= 0 ")
)

 

Pure virtual functions are not common, but they are not common for pure virtual destructor..

 

The objective of object-oriented design is to confirm useful abstractions and force them (and only them) into an abstract class.

 

I don't know how to predict how an inheritance system will be used in the future, but I know one thing: what an abstraction may be used in a context is just a coincidence, however, it is often meaningful to abstract multiple contexts.

 

Remember, you can benefit from an abstract class only when the designed class can be inherited by a future class without any modification. (If it needs to be modified, You have to recompile all the code using the inherited class, and you will not be able to get any benefit .)

 

When you find that you need to derive another specific class from a specific class, this is not the only place where the abstract class needs to be introduced. However, two classes need to be linked through public inheritance, which usually indicates a new abstract class.

 

 

Item34. understand how to mix C ++ and C in the same program:

 

Before using C ++ and C together in the same program, make sure that your c ++ compiler is compatible with the C compiler.

After the compatibility is confirmed, there are four issues to consider: Name mangling, static variable initialization, dynamic memory allocation, and data structure compatibility.

 

Name adaptation

 

That is, the C ++ compiler gives each function of a program a unique name. In C, this process is not required because there is no function overload, but almost all c ++ programs have function names (for example, the stream library declares several versions of operator <and operator> ). Reload is not compatible with most linked programs, because linked programs generally cannot distinguish functions with the same name. Name conversion compromises the linked program. The linked program usually insists that the function name must be unique.

 

If it is only within the range of C ++, name adaptation will not affect you. If you have a function called drawline and the compiler converts it to xy‑line, you always use the name drawline and won't notice that the OBJ file references xy‑line.

 

If drawline is in the C Runtime Library, the situation is different. The header file contained in your c ++ source file is declared:
Void drawline (INT X1, int Y1, int X2, int Y2 );

In the Code body, drawline is also called. Every such call is converted by the compiler to the function after the name transformation.
Drawline (A, B, C, D); // call to unmangled function name
The OBJ file calls the following:
Xywise (A, B, C, D); // call to mangled function Mame
However, if drawline is a C function, the compiled drawline function in the OBJ file (or a file such as a dynamic link library) is still called drawline; there is no name transformation action. When you try to link the OBJ file to a program, you will get an error because the linked program is looking for a function called xyruntime without such a function.

 

To solve this problem, you need a method to tell the C ++ compiler not to perform name transformation in this function. If you call a C function named drawline, it is actually called drawline. Your OBJ file should contain such a reference, instead of referencing a version with name transformation.

To disable name adaptation, use the extern "C" of C ++ to indicate:

// Declare a function called drawline; don't mangle <br/> // Its name <br/> extern "C" <br/> void drawline (INT X1, int Y1, int X2, int Y2); <br/>

Extern "C" can take effect for a group of functions, as long as they are placed in a pair of braces:

Extern "C" {// disable name mangling for <br/> // all the following funwing <br/> void drawline (INT X1, int Y1, int X2, int Y2); <br/> void twiddlebits (unsigned char bits); <br/> void simulate (INT iterations); <br/>... <br/>}

When compiling with C ++, you should add extern 'C', but this should not be the case when compiling with C.
By defining the macro _ cplusplus only under the C ++ compiler, you can organize the header file as follows:

# Ifdef _ cplusplus <br/> extern "C" {<br/> # endif <br/> void drawline (INT X1, int Y1, int X2, int Y2 ); <br/> void twiddlebits (unsigned char bits); <br/> void simulate (INT iterations); <br/>... <br/> # ifdef _ cplusplus <br/>}< br/> # endif

 

Static variable Initialization

 

For C ++, a large amount of code can be executed before and after the main execution. In particular, static class objects and constructors Defining class objects in a global, namespace, or file body are usually called before the main is executed. This process is called static initialization (see item e47 ). This is opposite to our general understanding of C ++ and C Programs. We always regard main as the entry point of the program. Similarly, the object generated through static initialization also needs to call its destructor during the static destructor process. This process usually occurs after the main end of running.

 

In order to solve the dilemma that the main () should be called first, and the object needs to be constructed before the main () Execution, many compilers () A special function is inserted at the very beginning, which is responsible for static initialization. Similarly, the compiler inserts a function at the end of main () to analyze static objects. The generated code usually looks like this:

Int main (INT argc, char * argv []) <br/>{< br/> initialize mstaticinitialization (); // generated by the <br/> // implementation <br/> the statements you put in main go here; <br/> define mstaticdestruction (); // generated by the <br/> // implementation <br/>}

If possible, use C ++ to write the main function. You only need to change the name of main () written in C to realmain (), and then use main () of C ++ to call realmain ():

Extern "C" // implement this <br/> int realmain (INT argc, char * argv []); // function in C <br/> int main (INT argc, char * argv []) // write this in C ++ <br/>{< br/> return realmain (argc, argv); <br/>}

In doing so, it is best to add comments to explain the reasons.

 

If you cannot use C ++ to write main (), it will be troublesome, because there is no other way to ensure that the construction and destructor of static objects are called. It is generally implemented by the compiler.

 

Dynamic Memory Allocation

 

C ++ uses new and delete (see item M8), and C uses malloc (or its deformation) and free. As long as the new memory is released using Delete and the memory allocated by malloc is released using free, there is no problem. Use free to release memory allocated by new or delete to release memory allocated by malloc. The action is not defined. The only thing to remember is to strictly isolate your new and delete from mallco and free.

 

Take a look at this rough (but very convenient) strdup function, which is not in the C and C ++ standard (Runtime Library), but is very common:
Char * strdup (const char * PS); // return a copy of
// String pointed to by PS
To avoid Memory leakage, strdup calls must release the memory allocated in strdup. But how can this memory be released? Delete? Use free? If the strdup you call comes from the C function library, it is the latter. If it is written in C ++, I am afraid it is the former.

 

To reduce this portability problem, we should try to avoid calling functions that are neither in the standard Runtime Library (see item e49 and item m35) nor in a fixed form on most computer platforms.

 

Compatibility of data structures

 

C understands common pointers, so I want to make your c ++ and C compilers produce compatible output, functions in the two languages can safely exchange pointers to objects and to non-member or static member functions. Naturally, struct and built-in variables (such as int and char) can also pass freely.

 

If you add a non-virtual function in C ++, the memory structure does not change. Therefore, only the non-virtual function structure (or Class) objects are compatible with their twin versions in C (the definition only removes the declarations of these member functions ). Adding a virtual function breaks this compatibility because its object uses a different memory structure (see item M24 ). Struct inherited from other struct (or class) usually also changes its memory structure, so struct with a base class cannot interact with C functions.

 

The conclusion is: if a structure definition can be compiled in both C ++ and C, it is safe to transmit data structures between C ++ and C in this way. Adding non-virtual member functions in C ++ does not affect compatibility, but almost any other changes will affect compatibility.

 

Summary
If you want to mix C ++ and C Programming in the same program, remember the following guiding principles:
① * Ensure that the C ++ and C compilers generate compatible OBJ files.
② * Declare the function used in both languages as extern "C ".
③ * Use C ++ to write main () whenever possible ().
④ * The total memory allocated by new is released using Delete; the total memory allocated by malloc is released using free.
⑤ * Restrict the content transmitted between the two languages to the scope of the data structure compiled by C. The C ++ version of struct can contain non-virtual member functions.

 

Item35. familiarize yourself with the c ++ language standards:

 

Major changes in C ++:
* New features are added: rtti, namespace, bool, mutable keyword, and explicit. The reload operation on enumeration has been initiated as well as the const static member variable in the class definition.
* The template has been extended: Now the Member template is allowed and the syntax for forced template instantiation is added. The template function allows no type parameters. The template class can use them as template parameters.
* Exception handling is refined: The exception specification statement is checked more strictly during compilation. The unexpected () function can now throw a bad_exception object.
* The memory allocation function is improved: Operator new [] and operator Delete [] functions are added. Operator new/new [] throws an exception when memory allocation fails, and there is a version that returns 0 (no exception is thrown) for selection. (See Objective C ++ Item 7)
* Added the following types: static_cast, dynamic_cast, const_cast, and reinterpret_cast.
* Language rules are redefined: When a virtual function is redefined, the returned value does not need to be completely matched.

 

These changes in C ++ will be eclipsed by the changes in the standard Runtime Library.
* Supports the Standard C Runtime Library.
* The string type is supported.
* Supports localization.
* Supports I/O operations.
* Supports mathematical operations.
* Supports common containers and operations.

 

Before introducing STL, you must first understand the two features of the Standard C ++ Runtime Library.
First, almost everything in the Runtime Library is a template. Second, the standard Runtime Library contains almost all the content in the namespace STD.

 

STL is based on three basic concepts: container, iterator, and algorithm ).

Another point: STL is extensible.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.