(Proactive C ++) Chapter 5 Implementation (Implementation)

Source: Internet
Author: User
7.1 Clause 26: delay the occurrence time of the variable definition (postpone variable definitions as long as possible)

As long as you define a variable and its type carries a constructor or destructor, when the control flow of the Program reaches the variable definition, you have to bear the constructor cost; when this variable leaves the scope, you have to bear the analysis component.
// This function prematurely defines the variable "encrypted"
STD: String encryptpassword (const STD: string & password)
{
Use namespace STD;
String encrypted;
If (password. Length () <passwd_min_len)
{
Throw logic_error ("password is too short ");
}
...
Return encrypted;
}
Example 7-1-1 The encryption function prematurely defines the variable
If an exception is thrown out, it is not actually used, but the encrypted structure and analysis structure are still paid. More popular practices:
STD: String encryptpassword (const STD: string & password)
{
... // Check the length
String encrypted (password); // defines and initializes a copy constructor.
Encrypt (encrypted)
Return encrypted;
}
Example 7-1-2 improved encryption function
This code has two improvements: one is to specify the initial value during construction, which is more efficient than constructing the object first and then assigning values; the other is to delay definition, the structure and analysis structure of the check length throwing exception is omitted.
But what about loops?
// Method A: defined in Vitro
Widget W;
For (INT I = 0; I <n; I ++)
{
W = depends on the value of I ;//
...
} // Method B: defined in circular body

For (INT I = 0; I <n; I ++)
{
Widget W (depending on the value of I );//
...
}
Example 7-1-3 Loop body and variable definition
Method A: 1 constructor + 1 destructor + N assignment operations
Practice B: N constructor + N destructor
Especially when the N value is great, A is generally more efficient.

Article 27: Minimize Casting)

One of the design goals of C ++ rules is to ensure that "type errors" are impossible. Unfortunately, the transformation (casting) destroys the type system ).
C-style transformation:
(T) expression // convert expression to T
Function style transformation:
T (expression) // convert expression to T
There is no difference between the two forms. We call this an old-style transformation (old-sytle casts ).
C ++ also provides four new transformations:
Const_cast (t) (expression)
Dynamic_cast (t) (expression)
Reinterpret _ cast (t) (expression)
Static_cast (t) (expression)
Different purposes:
? Const_cast is usually used to convert the constant of an object (cast away the constness ). Is the only C ++ sytle transformation operator with this capability.
? Dynamic is mainly used to determine whether an object belongs to a type in the inheritance system. The most costly transformation actions.
? The reinterpret is intended for low-level transformation. The actual action depends on the compiler, which means it cannot be transplanted.
? Static_cast is used to force implicit conversions (implicit conversion). For example, convert non-const to a cosnt object, or convert int to double. It can perform reverse conversion of the preceding conversions. For example, convert the void * pointer to the typed pointer and convert the pointer-to-base to the pointer-to-derived. However, const cannot be converted to non-Const. Only const-cast can achieve this.

Legacy conversions are still valid, but they are more popular.
The time to use legacy conversions is when I call an explicit it constructor to pass an object to a function. For example:
Class widget
{
Public:
Explicit widget (INT size );
};
Void dosomework (const widget & W );
Dosomework (widget (15); // create a widget with an int plus a function-style transformation action
Dosomework (static_cast <widget> (15); // create a widget with an int plus a C ++ style transformation action
Example 7-2-1 explain it Constructor
Any type conversion always really requires the compiler to compile the code for computation during the runtime.
Class base {...};
Class derived: public base {...};
Derived D;
Base * pb = & D; // implicitly convert derived * to base * // hidden danger
This example shows that a single object (for example, an object of the derived type) may have more than one address (for example, the address when "base * points to it" and the address when "derived * points to it ). Even in a single inheritance. The layout of objects and their address calculation methods vary with the compiler, which means that it works on a certain platform, but cannot be implemented on other platforms.
Class window
{
Public:
Virtual void onreszie (){...}
};
Class specialwindow: public window
{
Public:
Virtual void onreszie ()
{
Stat static_cast <WINDOW> (* This). onresize (); // convert * This to window and call
Onresz onreszie
... // Specialwindow exclusive Behavior
}
};
Example 7-2-2 wrong transformation action
The above example shows that it calls not the function on the current object, but the onresize on the temporary copy of the "* this base class component of the object" created by the transformation action earlier! It calls window: onresize on the copy of "base class composition of the current object" and then executes the exclusive specialwindow action on the current object. If window: onreszie modifies the object content, the current object is not changed, but the copy is changed. However, if specialwindow: onreszie also modifies the object, the current object will be modified. This current object enters a "disability" State: its base
The CLAS component changes were not implemented, while the derived class component changes were implemented.
The solution is to remove the transformation action.
Class specialwindow: public window
{
Public:
Virtual void onreszie ()
{
Stat window: onresize (); // call window: onresize to act on * This.
... // Specialwindow exclusive Behavior
}
};
Example 7-2-3 get the wrong transformation action
Dynamic_cast is usually required because you want to execute the derived class operation function on an object identified as derived class, but you have only one pointer reference pointing to the base, you can only rely on them to process objects. There are two general practices to avoid this problem:
First, using containers and storing them directly directing to the creation of the derived class Object eliminates the need to process objects through the base class interface.
Class window {...};
Class specialwindow: public window
{
Public:
Void blink ();
...
};
Typedef STD: vector <STD: tr1: shared_ptr <Windows> vpw;
Vpw winptrs;
...
For (vpw: iterator iter = winptrs. Begin (); iter! = Winptrs. End (); ITER ++)
{
// Do not want to use dynamic_cast
If (specialwindow * psw = dynamic_cast <specialwindow *> (ITER-> get ()))
{
Psw-> blink ();
}
}
// It should be modified to do so.
Typedef STD: vector <STD: tr1: shared_ptr <specialwindow> vpsw;
Vpsw winptrs;
For (vpsw: iterator iter = winptrs. Begin (); iter! = Winptrs. End (); ITER ++)
{
(* ITER)-> blink (); // OK
}
Example 7-2-3 use the container to store the derived object pointer
Second, let you use the base class interface to process "all possible windows Derived classes", that is, provide virtual functions in the base class to do what you want to do for various windows Derived classes.
Class window
{
Public:
Virtual void Bink (){}
};
Class specialwindow: public window
{
Public:
Virtual void blink (){};
};
Typedef STD: vector <STD: tr1: shared_ptr <Windows> vpw;
Vpw winptrs;
...
For (vpw: iterator iter = winptrs. Begin (); iter! = Winptrs. End (); ITER ++)
{
// There is no dynamic_cast
(* ITER)-> blink ();

}
Example 7-2-4 Use a virtual function to avoid Transformation
One thing that must be avoided is the so-called "rolling (cascading) dynamic_cast", as follows:

Class window {};
Class specialwindow1: public window {};
...
Typedef STD: vector <STD: tr1: shared_ptr <Windows> vpw;
Vpw winptrs;
...
For (vpw: iterator iter = winptrs. Begin (); iter! = Winptrs. End (); ITER ++)
{
// Define dynamic_cast
If (specialwindow * psw1 = dynamic_cast <specialwindow1 *> (ITER-> get ()))
{}
Else if (specialwindow * psw2 = dynamic_cast <specialwindow2 *> (ITER-> get ()))
{}
Else if (specialwindow * psw2 = dynamic_cast <specialwindow2 *> (ITER-> get ()))
{}
...
}
Example 7-2-5 transforming dynamic_cast

7.3 Clause 28: avoid returning handles pointing to the internal components of the object (avoid returning "handles" to object internals)

? Notes
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 "dangling handles.
Suppose there is such a rectangle:

Class Point {// This class represents a vertex
Public:
Point (int x, int y );
Void setx ();
Void sety ();
};
Struct rectdata {
Point ulhc; // upper left corner (upper left-hand corner)
Point lrlhc; // the lower right corner (lower right-hand corner)
}
Class rectangle {
Point & upperleft () const {return pdata-> ulhc ;}
Point & lowerright () const {return pdata-> lrhc ;}
PRIVATE:
STD: tr1: shared_ptr <rectdata> pdata;
};

// Used by the customer in this way
Point coord1 (0, 0 );
Point coord2 (100,100 );
Const rectangle Rec (coord1, coord2); // from (0, 0) to (100,100)
Rec. upperleft (). setx (50); // from (50, 0) to (100,100)
Example 7-3-1 Abnormal Encapsulation
Note that the caller of upperleft can use the returned reference to change the member, but rec should be immutable (const ). First, the encapsulation of member variables is at most equal to the access level of the function that "returns its reference. Second, if the const member function transmits a reference (non-const), the latter indicates that the data domain object itself is associated, and it is stored outside the object, then the caller of this function can modify the data.
Correct practice:
Class rectangle {
Cosnt point & upperleft () const {return pdata-> ulhc ;}
Cosnt point & lowerright () const {return pdata-> lrhc ;}
PRIVATE:
STD: tr1: shared_ptr <rectdata> pdata;
};
Example 7-3-2: normal Encapsulation
Even so, upperleft and lowerright still return handles representing the object (member variables and unpublished member functions), which may lead to dangling handles (an empty handle ). For example:

Class guiobject {...};
Const rectangle boundingbox (const guiobject & OBJ );
Guiobject * PGO;

Const point * pupperleft = & (boundingbox (* PGO). upperleft ());
Example 7-3-3 an empty handle
Let's assume that the caller of boundingbox (* PGO) obtains a new temporary rectangle object called temp. However, after temp. upperleft () is executed, temp will be destroyed, which indirectly leads to the points structure of temp. Eventually, pupperleft points to an existing object and becomes an empty or virtual hanging.

Article 29: Strive for exception-safe code)

Assume that a class is used to present a GUI menu with a background pattern and is used in a multi-threaded environment. Therefore, he has a mutex:

Class prettymenu {
Public:
...
Void changebackground (STD: istream & imgsrc );
...
PRIVATE:
Mutex ;//
Image * bgimage; // the current background image
Int imagechanges; // number of times the background image is changed
};
Void prettymenu: changebackground (STD: istream & imgsrc)
{
Lock (& mutex); // obtain the mutex (see clause 14)
Delete bgimage;
++ Imagechanges;
Bgimage = new image (imgsrc );
Unlock (& mutex );
}
Example 7-4-1 Non-exception Security Function
From the perspective of exception security, this function is very bad.
When an exception security throw, functions with exception security will:
? Do not disclose any data: The above does not do this, because once the new image (imgsrc) caused by exceptions, the unlock research will never be executed, so the mutex will always hold it.
? Data corruption is not allowed: if new image (imgsrc) throws an exception, bgimage executes a deleted object, and imagechanges is also accumulated.

Exception-safe functions provides one of the following three guarantees:
? Basic commitment: if an exception is thrown, everything in the program remains in the valid state.
? It is strongly guaranteed that the program State remains unchanged if an exception is thrown.
? Do not throw (nothrow) guarantee: Promise never throw an exception because they can always complete the functions they previously promised. All operations acting on built-in types (such as ints and pointers) provide the nothrow guarantee.

Therefore, our improvements are as follows:
Class prettymenu {
...
PRIVATE:
Mutex ;//
STD: tr1: shared_ptr <image> bgimage; // the current background image.
Int imagechanges; // number of times the background image is changed
};
Void prettymenu: changebackground (STD: istream & imgsrc)
{
Lock M1 (& mutex); // resource manager class (see clause 14)
// Set the internal pointer of bgimage using the new image execution Interface
Bgimage. Reset (new image (imgsrc ));
++ Imagechanges;
}
Example 7-4-2 Basic abnormal Security Function
Note: you do not need to manually delete the old image because it has been completed by the smart pointer. In addition, the delete action takes place only after the new image is successfully created. The tr1: shared_ptr: reset function is called only after its parameters are successfully generated. Delete is used only within the reset function, So delete is never used if it has never been in that function.
However, the imgsrc parameter is insufficient in the US. If the image constructor throws an exception, it is possible that the reading mark of the input stream has been removed.
The general design strategy is copy and swap. The principle is simple: Make a copy of the object you intend to modify, and then make all necessary modifications on that copy. In fact, all "data of subordinate objects" are put into another object from the original object, and then a pointer is given to the original object to execute the so-called implementation object (Implementation object, that is, a copy ). This technique is called pimpl idiom, which is detailed in Clause 31. The typical syntax is as follows:
Struct pmimpl {// pmimpl = prettymenu impl
STD: tr1: shared_ptr <image> bgimage; // the current background image.
Int imagechanges; // number of times the background image is changed
};
Class prettymenu {
...
PRIVATE:
Mutex ;//
STD: tr1: shared_ptr <pmimpl> pimpl;
};
Void prettymenu: changebackground (STD: istream & imgsrc)
{
Using STD: swap; // See Clause 25.
Lock M1 (& mutex); // resource manager class (see clause 14)
STD: tr1: shared_ptr <pmimpl> pnew (New pmimpl (* pimpl ));//
Pnew-> bgimage. Reset (new image (imgsrc); // modify the copy
++ Pnew-> imagechanges;
Swap (pimpl, pnew); // replace data and release mutex
}
Example 7-4-3 Strongly abnormal Security Function
In this example, we choose to make pmimpl a struct instead of a class, because the data encapsulation of prettymenu has been guaranteed because "pimpl is private.

7.5 Clause 30: thoroughly understand the inlining (understand the ins and outs of inlining)

Writing inline functions is like a real life without a white lunch. The overall idea behind the inline function is to replace "every call to this function" with the function ontology. In this way, increase the size of your target code. If you are too keen on inlining on a limited memory machine, the program size will be too large. Even if you have virtual memory, the Code expansion caused by inline will lead to additional page feed, reduces the hit rate of the instruction caching device, thus reducing the efficiency.
Remember, inline is just an application for the compiler, not a mandatory command. This application can be either proposed in a metaphor or explicitly.

Class person {
Public:
Int age () const {return theage} // an inline application for metaphor. Age is defined in class.
PRIVATE:
Int ethage;
};
Example 7-5-1 applying for an inline metaphor
Such a function is usually a member function, but cla46 states that the friend function can also be defined in the class. If so, they are also declared as inline by metaphor.
The following is the implementation of the standard Max template (from <algorithm>:
Template <typename T>
Inline const T & STD: max (const T & A, const T & B ){
Return a <B? B:;
};
Example 7-5-2 apply for a display inline
The embodiment of the template has nothing to do with inlining.
Whether a seemingly inline function is actually inline depends on your build environment and the compiler. Fortunately, most compilers provide a diagnostic level: if they cannot put the inline function you requested, they will give you a warning (see article 53 ).
If the program needs to obtain the address of an inline function, the compiler usually needs to generate an outlined function Ontology for this function. For example:
Inline void F (){...} // Assume that the compiler is willing to "call f" in inline"
Void (* PF) () = F; // pf points to F
...
F (); // this call will be inlined because it is a normal call
PF (); // This call may not be inlined because it is achieved through the function pointer.

The library designer must evaluate the impact of "declaring a function as inline". The inline function cannot be upgraded as the library is upgraded. Once F is changed in the library design, all the client programs that use F must be re-compiled.
Do not forget the 80-20 rule of thumb: on average, a program usually spends 80% of the execution time on 20% of the Code. This is an important rule.

7.6 Clause 31: Minimize the compilation dependency between files (minimize compilation dependencies between files)

C ++ has not done a good job of separating interfaces from implementations. The class definition not only describes the class interface in detail, but also includes full implementation details, such:

Class person {
Public:
Person (const STD: string & name, const Date & birthday, const address & ADDR );
STD: string name const;
STD: String birthdate () const;
STD: String address () const;
PRIVATE:
STD: String thename; // Implementation Details
Date thebirthdate; // Implementation Details
Address theaddress; // Implementation Details
};
Example 7-6-1 interface and implementation are not separated
The class person here cannot be compiled unless the class string, date, and address definitions used for its implementation code are obtained. Such definitions are usually provided by the # include indicator, such as the following:
# Include <string>
# Include "date. H"
# Include "address. H"
We can do this for person: Divide person into two classes, one provides only the interface (person), and the other assigns values to implement this interface (personimpl ).

# Include <stirng> // standard library components should not be pre-declared
# Include <memory>
Class personimpl; // The pre-declaration of the person implementation class
Class date; // The pre-declaration of the classes used by the person Interface
Class address ;//
Class person {
Public:
Person (const STD: string & name, const Date & birthday, const address & ADDR );
STD: string name const;
STD: String birthdate () const;
STD: String address () const;
PRIVATE:
STD: tr1: shared_ptr <personimpl> pimpl; // pointer, pointing to implementation class, see section 13
};
Example 7-6-2: handle classes)
This is the real "interface and implementation separation ". The key to this separation is to replace "defined dependency" with "declared dependency", which is the essence of minimizing compilation dependency. Everything else comes from this simple design strategy:
? If you can use object reference or object pointers to complete the task, do not use objects.
? If possible, replace the class definition with the class declaration. For example
Class date;
Date today ();
Void clearappointments (date D );
Declare the today and clearappointments functions without defining the date.
? Different header files are provided for declarative and defined headers. Of course, these files must be consistent. If one declarative file is changed, both files must be changed. Library customers should always # inlucde a declaration file.
C ++ also provides the keyword export, allowing the template declarative and template definitions to be separated from different files. Unfortunately, few compilers support this keyword.
The use of pimpl idiom classes like person is called handle classes. Another way to create handle classes is to make person a special abstract base class, called interface class. As follows:
Class person {
Public:
Person (const STD: string & name, const Date & birthday, const address & ADDR );
Static STD: TRL: shared_ptr <person> // Clause 18 and Clause 13, factory function
Create (const STD: string & name,
Const Date & birthday,
Const address & ADDR );
Virtual ~ Person ();
Virtual STD: string name const = 0;
Virtual STD: String birthdate () const = 0;
Virtual STD: String address () const = 0;
};
STD: TRL: shared_ptr <person> // Clause 18 and Clause 13, factory function
Person: Create (const STD: string & name,
Const Date & birthday,
Const address & ADDR)
{
Return
STD: TRL: shared_ptr <person> (New realperson (name, birthday, address ));
}
Class realperson: public person {
Public:
Realperson (const STD: string & name, const Date & birthday,
Const address & ADDR): thename (name), thebirthday (birthday ),
Theaddress (ADDR)
{
}
Virtual ~ Realperson ();
STD: string name const;
STD: String birthdate () const;
STD: String address () const;
};

// Used by the customer in this way
STD: string name;
Date dateofbirth;
Address;
// Create an object
STD: TRL: shared_ptr <person> PP (person: Create (name, birthday, address ));
STD: cout <PP-> name () <Endl;
Example 7-6-3 interface and implementation separation (interface classes)
Of course, the specific class (concrete classes) that supports the interface class interface must be defined and the real constructor must be called. Realperson demonstrates one of the two most common mechanisms for implementing the Interface Class: Inheriting the interface specification from the interface class, and then implementing the functions covered by the interface. The second is to implement multi-inheritance of design and the topic explored in Clause 40.
Handle classes and interface classes remove the coupling between interfaces and implementations, thus reducing the compilation dependency between files.

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.