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.
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.
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.
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.