Clause 26: try to delay the appearance of the variable definition
Blog: http://www.cnblogs.com/ronny/Reprinted please indicate the source!
Some objects may be defined too early, but they are often imported during code execution. As a result, the defined objects are not used, the constructor has made a cost to analyze the composition.
So we should try to extend the definition as much as possible when defining the object, or even until the moment before the variable is used, until the initial value can be provided.
The advantage of doing so is that it not only avoids unnecessary (destructor) objects, but also avoids meaningless default constructor behavior.
What should I do in a loop? At this time, we usually have two options:
Method A: 1 constructor + 1 destructor + n assignment operations // variable determination outside the loop and assignment within the loop
Practice B: n constructors + n destructor // define and initialize variables in the loop
At this time, it is estimated that the cost of value assignment is low or the cost of structure + structure is low. In addition, it is worth considering the issue of object scope.
Article 27: Minimize transformation actions
There are usually three different forms of transformation Syntax:
1. C-style transformation: (T) expression
2. function-style transformation: T (expression)
3. The second type above is called "Old-style transformation". C ++ provides four new types of Transformation:
Const_cast <T> (expression) // It is usually used to convert the object's constants to Division
Dynamic_cast <T> (expression) // converts an object to a subclass to determine whether it belongs to a type in the inheritance system, but it consumes significant operation costs.
Reinterpret_cast <T> (expression) // executes a low-level transformation. The actual action depends on the compiler, which means it cannot be transplanted. For example, convert a point to int into an int
Static_cast <T> (expression) // forced implicit conversion
In the design of many derived classes, the virtual function of the derived class needs to call the virtual function of the base class. The following is an example. window is a base class, which defines a virtual function onResize, special Window is a derived class.
// Blog address: http://www.cnblogs.com/ronny/ reprint please indicate the source! Class Window {public: virtual void onResize () ;}; class SpecialWindow: public Window {public: virtual void onResize () {static_cast <Window> (* this ). onResize (); // convert * this to Window, and then call its onResize //... specialWindow exclusive action }};
But the Code calls the virtual function of the base class through transformation. In fact, static_cast <Window> (* this ). onResize () calls a copy onResize function of * this base class component. Therefore, the onResize operation can affect only one temporary object. The solution is to remove the transformation and replace it with Window: onResize ().
Remember:
If possible, avoid transformation as much as possible, especially avoid dynamic_cast in code that focuses on efficiency. If there is a design that requires transformation, try to develop a design that does not require transformation.
If transformation is necessary, try to hide it behind a function. The customer can then call the function without putting the transformation into their own code.
We would rather use the C ++ style (New style) transformation than the old style transformation. The former is easy to recognize and has a different role.
Clause 28: avoid returning the internal components of handles pointing to the object
Class Point {public: Point (int x, int y); void SetX (int newVal); void SetY (int newVal); private: int x_cor; int y_cor ;}; struct RectData {Point ulhc; // Point lrhc in the upper left corner of the Rectangle; // Point in the upper right corner of the Rectangle}; class Rectangle {private: shared_ptr <RectData> pData; public: point & upperLeft () const {return pData-> lrhc;} Point & lowerRight () const {return pData-> ulhc ;}};
In the above generation, Point indicates the midpoint of the coordinate system. RectData indicates the coordinate between the upper left corner and the lower right corner of a rectangle. Rectangle is a Rectangle class that contains a pointer to RectData.
We can see that uppLeft and lowerRight are two const member functions. They only want to provide two coordinate points related to Rectangle to the customer, rather than asking the customer to modify the Rectangle. However, both functions return references pointing to private internal data, so the caller can change internal data through references.
This gives us some warning: the encapsulation of member variables is only equal to the access level of the function that "returns its reference". If a reference is sent from the const member function, if the data referred to by the latter is associated with the object itself, and it is stored outside the object, the caller of this function can modify the data.
Handles (number plate used to obtain an object) refers to the reference, pointer, and iterator. They return a handle that "represents the internal data of the object.
We can add const to the response type of the above member functions to solve the problem:
public: const Point& upperLeft()const{ return pData->lrhc; } const Point& lowerRight()const{ return pData->ulhc; }
However, the function returns a handle, which indicates that the internal components of the object are always dangerous, because it may cause dangling handles (an empty number plate ). For example, a function returns the bounding box of the GUI object ).
class GUIObject{ //..};const Rectangle boundingBox(const GUIObject&obj); GUIObject* pgo;const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());
The call to boundingBox returns a temporary object with no name. Then we call the upperLeft object and return a reference pointing to the internal data of the temporary object. However, after the statement is completed, this temporary object will be destroyed, and pUpperLeft will become a suspended, virtual hanging (dangling )!
Remember
Avoid returning handles (including reference, pointer, iterator) to the object. Compliance with this clause can increase encapsulation, help const member functions behave like a const, and minimize the possibility of a "virtual hanging number plate.
Article 29: Efforts for "exceptional security" are worthwhile
Abnormal security functions do not leak resources or allow any data structure corruption even if an exception occurs. Such functions are classified into three possible guarantees: basic type, strong type, and no exception type.
Strong assurance is often implemented using copy-and-swap, but "strong assurance" is not feasible or practical for all functions.
The "exception Security Guarantee" provided by a function is usually the weakest among the "exception Security Guarantee" of each function that it calls.
Clause 30: A thorough understanding of inlining
About inline:
1. the inline function is called to the function body and expands the function. improper use may result in code expansion.
2. Most of the inline functions of C ++ programs are stored in the header file. inlining occurs during compilation.
3. the inline function only represents the "function ontology". There is no "function substance" and there is no function address.
It is worth noting that:
1. constructor and destructor are often not suitable for inline. Because both functions contain many implicit calls, the cost of these calls is worth considering. Code expansion may occur.
2. the inline function cannot be upgraded with the library upgrade. Because most of them occur during the compilation phase, the upgrade means re-compilation.
3. Most debuggers cannot set breakpoints in the inline function. Because the inline function does not have an address.
Remember
1. Most inlining restrictions are imposed on small and frequently called functions. This makes the debugging process and binary upgrade easier in the future, and minimizes potential code expansion problems, maximizing the program's speed increase.
2. In addition, exercise caution on the inline of function templates to ensure that all the implemented functions should be inlined and then inline.
Clause 31: Minimize the dependency between files.
This problem arises from the need to minimize the impact of compilation, improve compilation efficiency, and reduce maintenance costs.
To achieve this goal, the first thing that comes to mind is the separation of declaration and definition. Your use only depends on the declaration and does not depend on the definition (that is, the specific implementation ).
But the definition of C ++ Class is not only interface, but also implementation details (this refers to the private member required to implement the interface ). Sometimes we need to modify the implementation method of the interface, and this modification may need to add private variables, but this private variable should not be visible to users. However, this modification is placed in the header file of the definition, resulting in re-compilation of all the code of this object.
So there isPimpl (pointer to implementation). Use pimpl to hide the implementation details. Only one declaration is required in the header file, and this poniter is called as a private member variable.
Here is an interesting place. Why is Pointer used instead of a specific object? This requires the compiler, because the compiler needs to know the space size of the variable in advance when defining the variable, and does not know the size if it only gives a declaration but does not define it, the pointer size is fixed, so you can define the pointer (even if only one declaration is provided ).
In this way, the Implementation Details are hidden, so the change of the implementation method will not cause the re-Compilation of some other code. In addition, only impl class declaration is provided in the header file, and the basic implementation will not be visible to users, but also increases encapsulation.
The structure should be as follows:
class AImpl;class A {public: ...private: std::tr1::shared_ptr<AImpl> pImpl;};
This type is also calledHandle class
Another implementation method is to useInterface class. It is to write all interfaces as pure virtual and implement them in sub-classes, and generate instances through the factory function or virtual constructor.
When declaring a file, write the following:
class Person{public: static shared_ptr<Person> create(const string&, const Data&, const Adress&);};
Write the definition implementation file like this
class RealPerson :public Person{public: RealPerson(...); virtual ~RealPerson(){} //...private: // ...};
The above mentioned methods used for de-coupling will inevitably carry some performance sacrifices, but the author suggests that the above methods should be used in the development process, when the above method has a greater effect on speed and/or size than coupling, write a specific object to replace the above method.
Remember:
The general idea of supporting "minimal compilation dependency" is: dependent on declarative, do not trust the definition. The two methods based on this idea are Handles classes and Interface classes.
Library header files should exist in the form of "completely and only declarative", which applies whether or not templates is involved.