Tive C ++ Clause 31: minimizes the dependency between files.

Source: Internet
Author: User

Suppose you have made some minor changes to a class implementation file of the C ++ program. Instead of modifying the interface, you can implement it and only change the private component.

Then re-build the program and expect it to take only a few seconds. When you press "build" or type make, you will be surprised because you realize that the whole world has been re-compiled and linked!

The problem is that 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:

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
};

This class person cannot be compiled. The person definition file at the top may have something like this:

# Include <string>
# Include "date. H"
# Include "address. H"

In this way, a compilation dependency is formed between the person definition file and Its included files ). If either of these header files is changed or other header files on which these files depend are changed, then each file containing the person class must be re-compiled, any file that uses the person class must be re-compiled. Cascading compilation dependencies may cause unspeakable disasters for many projects.

Why does C ++ insist on placing the implementation details of the class in the class definition form? Why not define person in this way and separate the Implementation Details:

Namespace STD {class string;} // pre-declaration (incorrect)
Class date; // pre-declaration
Class address; // pre-declaration
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;
...
};

In this case, the person user recompiles the object only when the person interface is modified.

Two problems: first, string is not a class, it is a typedef. Therefore, the pre-declaration of string is incorrect, and you should not have manually declared some standard libraries. You should only use the appropriate # pair des for the purpose. Standard Header files are unlikely to become compilation bottlenecks,

Second, the compiler must know the object size during compilation:

Int main ()
{
Int X;
Person P (Params );
}

The compiler knows that there must be enough space allocated to place a person, but it must know the size of a person object. The only way to get this information is to ask the class definition. However, if the class definition can legally not List Implementation Details, how does the compiler know how much space to allocate?

This problem does not exist in languages such as Smalltalk and Java, because when we define objects in that language, the compiler only allocates enough space for a pointer (used to point to this object. That is to say, they treat the above Code as follows:

Int main ()
{
Int X;
Person * P;
}

This is of course a legal C ++ code, so you can play the game of "Hiding object implementation details behind a pointer. The person can be divided into two classes, one providing the interface and the other implementing the interface. The so-called implementation class to be implemented is named personimpl. The person is defined as follows:

# Include <string>
# Include <memory>
Class personimpl;
Class date;
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 to the implementation object
};

Here, person contains only one pointer member pointing to its implementation class (personimpl ). This design is often called pimpl idiom (pimpl is short for "pointer to Implementation ).

In this way, the customer of person is completely separated from the Implementation Details of date, address, and person. Any Implementation Modification of those classes does not require the person client to be re-compiled.

The key to this separation is to replace "defined dependency" with "declared dependency", which is the essence of minimizing compilation dependency: To make header files as self-satisfied as possible, in case they cannot be done, so that it is dependent on the declarative (rather than the defined) in other files. Everything else comes from this simple policy.

If you can use object reference or object pointer to complete the task, do not use objects.

You can define the pointer and reference pointing to this type only by declarative statements. However, if you define a type of objects, you need to use the definition of this type.

If possible, replace the class definition with the class declaration.

When you declare a function and use a class, you do not need the definition of the class, even if the function uses the by value method to pass Parameters of this type (or return values:

Class date; // class declarative
Date today ();
Void clearappiontments (date D );

The declared today and clearappointments functions do not need to define date, but once anyone calls those functions, the date definition must be exposed before calling. If you can transfer the obligation to "provide class definitions" (completed through # include) from the header file of "function declaration" to the customer file containing function calls, you can remove the compilation dependency between the "not really necessary type definition" and the client.

Different header files are provided for declarative and defined headers.

Therefore, the library client should always # inlcude a declaration file instead of a number of functions that are pre-declared,

# Include "datefwd, H" // declare class date in this header file
Date today ();
Void clearappointments (date D );

Only the declared header file is named "datefwd. H", like the header file of the standard library "<iosfwd> ". This clause also applies to non-templates ". Many template definitions are usually placed in header files in the build environment, but some build environments allow tamplates definitions to be placed in non-header files ", in this way, the header file containing only declarative files can be provided to templates.

This kind of pimpl idiom classes is often called handle classes.

One of the methods for this classes is to forward all their functions to the corresponding implementation class (Implementation classes) and the latter completes the actual work.

# Include "person. H"
# Include "personimpl. H"
Person: Person (const STD: string & name, const Date & birthday, const address & ADDR)
: Pimpl (New personimpl (name, birthday, ADDR ))
{}
STD: String person: Name () const
{
Return pimpl-> name ();
}

Another way to create a handle class is to make person a special abstract base class called interface classes. The purpose of this class is to describe the derived classes interface in detail. Therefore, it usually does not contain member variables or constructor. There is only one virtual destructor and a group of pure virtual functions, describes the entire interface.

An Interface Class written for person may look like this:

Class person {
Public:
Virtual ~ Person ();
Virtual STD: string name () const = 0;
Virtual STD: String birthday () const = 0;
Virtual STD: String address () const = 0;
...
};

The customer of this class must use the pointers and reference of the person to write the application, and the person classes containing the pure virtual function cannot reflect the entity. Unless the interface class interface is modified, its customers do not need to recompile.

The customer of interface class must have a way to create a new object for this class. They usually call a special function, which plays the role of a constructor of the derived class that "will actually be made available. It is usually called a factory function or virtual constructor. They return pointers pointing to the dynamically allocated objects that support the interface class interface. Such functions are often declared as static in interface class:

Class person {
Public:
...
Static STD: tr1: shared_ptr <person>
Create (const STD: string & name, const Date & birthday, const address & ADDR );
};

Customers may use them like this:

STD: string name;
Date datebirth;
Address;
STD: tr1: shared_ptr <person> PP (person: Create (name, datebirth, address ));
...
STD: cout <PP-> name ()
<"Was born on"
<PP-> birthdate ()
<"And now lives"
<PP-> address ();
...

Of course, the concrete classes that support the interface class interface must be defined, and the real constructor must be called.

Assume that there is a derived class realperson that provides the implementation of the inherited virtual function:

Class realperson: public person {
Public:
Realperson (const STD: string & name, const Date & birthday, const address & ADDR)
: Thename (name), thebirthdate (birthday), theaddress (ADDR)
{}
Virtual ~ Realperson (){}
STD: string name () const;
STD: String birthdate () const;
STD: String address () const;
PRIVATE:
STD: String thename;
Date thebirthdate;
Address theaddress;
};

With realperson, writing "Person: Create" is not uncommon:

STD: tr1: shared_ptr <person> person: Create (const STD: string & name, const Date & birthday, const address & ADDR)
{
Return STD: tr1: shared_ptr <person> (New realperson (name, birthday, ADDR ));
}

A more realistic person: The create implementation code will create different types of derived class objects, depending on such additional parameter values, data in a single file or database, environment variables, and so on.

Realperson demonstrates implementing one of the two most common interfaces of Interface Class: Inheriting interface specifications from interface class, and then implementing functions covered by interfaces.

Handle classes and interface classes remove the coupling between interfaces and implementations, thus reducing the compilation dependency between files.

In handle classed, the member function must use implementation pointer to obtain object data. This adds an indirect layer for each access. The size of implementation pointer must be increased for the memory consumed by each object. Implementation pointer must Initialize an implementation object pointing to a dynamic allocation, so it also suffers from additional overhead caused by dynamic memory allocation.

Interface classes. Since each function is virtual, an indirect hop must be made for each function call. In addition, the object derived from interface class must contain a vptr (virtual table pointer ).

The use of handle class and Interface Class in the Process of program development will minimize the impact on customers when the implementation code changes.

When they cause too much difference in speed and/or size so that the coupling between classes is not critical, handle class and interface class are replaced with concrete class.

The general idea of supporting "minimal compilation dependency" is: dependent on declarative statements, not definitions.

Library header files should exist in the form of "secure and declarative" (full and declaration-only forms. This method applies regardless of whether templates is involved.

Related Article

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.