Gotw # 07 Compile-Time Dependencies
Author: Herb Sutter
Translation: Kingofark
[Declaration]: This article takes the Guru of The Week column on www.gotw.ca website, and its copyright belongs to the original person. Translator Kingofark translated this article without the consent of the original person. This translation is only for self-study and reference, please read this article, do not reprint, spread this translation; people download this translation content, please delete its backup immediately after reading. Translator Kingofark is not responsible for people who violate the above two principles. This declaration.
Revision 1.0
GURU of The Week Terms 07: Dependence
Difficulty: 7/10
(Most programmers use the header files contained in #include more than the actual needs. Do you have this? If you want to know, please see the Terms.)
[problem]
[Note: This problem is more difficult than imagined! The comments in the following procedures are very useful. ]
Most programmers use the header files contained in #include than the actual needs. This will seriously affect and extend the build time, especially when a frequently used header file contains too many other header files, the problem is more serious.
First, in the following header file, which #include statement can be removed directly without the side effects of the program? Second, what is the #include statement can be removed after proper modification of the program? How will the program modified? (You can't change the common interface of the X-class and Y class; that is, any modifications you make to this header cannot affect the code called it).
// gotw007.h (Implementation File is gotw007.cpp)
//
#include "a.h" // Class A
#include "b.h" // Class B
#include "c.h" // Class C
#include "d.h" // Class D
// (Note: Only A and C have virtual functions (Virtual functions))
#include
#include
#include
#include
#include
Class x: public a {
PUBLIC:
X (const C &);
D Function1 (int, char *);
D function1 (int, c);
B & Function2 (B);
Void Function3 (std :: wostringstream);
Std :: Ostream & Print (std :: ostream);
Private:
Std :: string name_;
Std :: List
D D_;
}
Std :: Ostream & Operator << (std :: ostream & os, const x)
{RETURN X.Print (OS);
Class Y: private b {
PUBLIC:
C function4 (a);
Private:
Std: List
[answer]
First, we consider the #include statement (or header file) that can be removed directly. For easy viewing, let's take the original code below:
// gotw007.h (which implements the file is gotw007.cpp)
//
#include "a.h" // Class A
#include "b.h" // Class B
#include "c.h" // Class C
#include "d.h" // Class D
// (Note: Only A and C have virtual functions (Virtual functions))
#include
#include
#include
#include
#include
Class x: public a {
PUBLIC:
X (const C &);
D Function1 (int, char *);
D function1 (int, c);
B & Function2 (B);
Void Function3 (std :: wostringstream);
Std :: Ostream & Print (std :: ostream);
Private:
Std :: string name_;
Std :: List
D D_;
}
Std :: Ostream & Operator << (std :: ostream & os, const x)
{RETURN X.Print (OS);
Class Y: private b {
PUBLIC:
C function4 (a);
Private:
Std :: List
}
1. We can go directly to the header files:
L iostream, because of the program, it is not used in iostream, although the stream is used.
L Ostream and SSTREAM, because the parameters and return types in the program are available (Forward-declared), it is only necessary for iOSFWD (to be noted, there is no such as StringFWD or LISTFWD corresponding to iOSFWD. The standard header file of the class; IOSFWD is a product taken into account the downwardly compatibility problem, which makes the code that previously not supported the template, but does not need to be modified or rewritten.)
We can't go directly to the header files:
L A.H, because A is the base class of X.
l B.H, because B is the base class of Y.
l C.h, because many of the existing compilers require List
L D.H, List and String, because x needs to know that the size of D and String, X and Y need to know the size of the List.
Second, let's examine the #include statement (or header file) that can be removed by hiding the details of X and Y.
2. We can remove DH, List, and String by let X and Y using PIMPL_ method (that is, its private part is replaced by a pointer, this pointer points to the entity object of the forward-declared), Because at this time, X and Y no longer need to know the size of List, D or String. This also allows us to do off C.h because the C object in x :: clist appears as a parameter or return value. Important: Even if ostream is not defined, the inline Operator << may still maintain its inline and use its Ostream parameters! This is because you only need the corresponding definition only when calling the member function; when you want to receive an object, you only make any other extra thing when you call the parameters when you call other functions, you and The definition of this function is not required.
Finally, let's take a look at the header files that can be removed by other minor modifications:
3. We noticed that the Private base class of Y, and B has no virtual function, so B.H is also possible to be removed. There is a main reason for us to use private inheritance when we are derived, that is, you want the override virtual function (Virtual function). In this point, here is here to let Y inheritance from B, it is better to let Y have a member of a type B. To remove B.H, we should let Y's type B groups are in the PIMPL_ section hidden in Y.
[Learning Guidance]: Please use PIMPL_ to isolate the caller's caller with the implementation details of the code.
Extract the coding standard from GOTW:
- Encapsulation and INSULATION:
- When declaring a class, you should avoid exposure to its private member:
- You should use an opaque pointer such as "struct xxxxlmpl * pimpl_" to store private members (including status variables and member functions), for example:
Class Map {private: struct maplmpl * pimpl_;};
(Lakos96: 398-405; Meyers92: 111-116; Murray93: 72-74)
4. Based on the following reasons, we are currently unable to be used as a.h hand-on: A is used as a public base class; a Virtual function, so its IS-A relationship may be used by the caller. However, we noticed that there is no relationship between the two classes of X and Y, so we can at least put the definitions of X and Y in the middle of the two different header files (in order not to affect the existing code, we should also Use the existing header file as a stub, allowing it to include xh and yh in #include). In this way, we can at least let Y.h do not have to include A.h because it is now only used as the type of function parameters, and does not require a definition.
In summary, we can now get a refreshing header file:
/ / -------------------------------------------------------------------------------------------- ---------------
// New file x.h: Only two #include!
//
#include "a.h" // Class A # include
Class C;
Class D;
Class x: public a {
PUBLIC:
X (const C &);
D Function1 (int, char *);
D function1 (int, c);
B & Function2 (B);
Void Function3 (std :: wostringstream);
Std :: Ostream & Print (std :: ostream);
Private:
Class XIMPL * PIMPL_;
}
Inline std :: ostream & operator << (std :: ostream & OS, const X)
{RETURN X.Print (OS);
// Note: There is no need for Ostream definition!
/ / -------------------------------------------------------------------------------------------- ---------------
// New file y.h: No #include!
//
Class A;
Class C;
Class y {
PUBLIC:
C function4 (a);
Private:
Class YIMPL * PIMPL_;
}
/ / -------------------------------------------------------------------------------------------- ---------------
// gotw007.h As the stub contains two #include, and the other two #include included with X.h
//
#include "x.h"
#include "y.h"
/ / -------------------------------------------------------------------------------------------- ---------------
// gotw007.cpp ... Note: IMPL objects should be in the X and Y constructor
// Create with new and use delete in the destructor of X and Y.
// X and Y members of the member to access data through the PIMPL_ pointer
//
Struct Ximpl // Yes, we can use "struct", although the front declaration
{// we used "class"
Std :: string name_;
Std :: List
D D_;
}
Struct yimpl
{
Std :: List
B b_;
}
Finally, say a few words: now, the user only needs to use #include to contain A.H and IOSFWD. The user only needs to include A.H and IOSFWD, even if you need to contain Y.H and remove GOTW007.h later, it is not necessary to add multiple questions. How much improvements are this compared to the original procedures!