I encountered a post on csdn. The basic description is as follows:
// A.h # Ifndef a_h_included # Define A_h_included Class A { Private : B ;}; # Endif // A_h_included // A. cpp is just an example. It doesn't make any sense. # Include " A.h " // B. H # Ifndef B _h_included # Define B _h_included Class B { Private : ;}; # Endif // B _h_included // B. cpp # Include " B. H " // Main. cpp # Include <iostream> Using Namespace STD; Int Main (){ Return 0 ;}
In fact, the solution is very simple. Just save the pointer of the other party in the class.
Next I will post two related articlesArticle, The original isEric Z's program lifeWritten.
Article 1:
Incomplete type and foreward Declaration
Sometimes we encounter some compiler errors related to incomplete types during programming. At this time, we often simply change it to the corresponding complete type definition, and we don't have time to think about why the error is reported, there are no other better solutions; there are also many people who will include all the header files that can be included in November 21, regardless of whether they are compiled. In many cases, a custom type does not need to contain its header file. Therefore, I wrote an article today to summarize these issues.
Incomplete type
Incomplete types, including incompletely-Defined Object Type and void ). We all know that the null type is mainly used in function return values and null pointers. The former is the focus of today's research. It is a fuzzy type that is unknown to all sizes, layout, and alignment requirements.
The following lists some common incomplete types:
//Main. cpp//Variable definition. Because the array size is unknown, compilation fails.CharA [];//Variable definition, because type A is undefined and cannot be compiledA B;//Variable definition. Although the size is determined, type A is not defined and cannot be compiled.A c [10];IntMain (){}
These global objects only provide incomplete types when being defined, and their size and other information compilers cannot be informed, so they cannot be compiled. What is the use of incomplete types? The following is a small example.
// A. cpp Char A [ 10 ] = " 123456789 " ; // Main. cpp # Include <iostream> Using Namespace STD; // Variable declaration, and is an incomplete type Extern Char A []; Int Main (){ // Compiled successfully and printed from 1 to 9 For ( Int I = 0 ; A [I]! = ' \ 0 ' ; ++ I) cout <A [I] < Endl; // The following compilation failed. // Cout <sizeof (a) <Endl; }
Here, we found that incomplete types can be used in declarations rather than definitions. Because the Declaration does not need to know the object size, memory layout, and other information. In addition, we also found that although the declared object of the incomplete type has no problem, the subsequent operations determine whether the object can be compiled. Print the element section of the array, because the element type (char) is known and does not need to know its size (because we determine whether it ends Based on the ending NUL character ), so there is no problem with compilation. However, if you need to print the array size, the compilation will fail becauseAOr incomplete type.
The above example only describes the use of incomplete types and is not very practical. Because other arrays (such as integer arrays) do not end with special characters (such as NUL. If we don't even know the size of the array, what does it mean to operate on it? Therefore, in actual projects, it is more common to declare using the complete type, such:
Extern CharA [10];
Forward Declaration
The real application appears in the forward Declaration of the class.
// . h class {...} // B. h /// Forward Declaration class ; class B { private : // The following a types are incomplete A * M_a; int calculate ( const A & A1, const A & A2) ;}
I wonder if you have found that B .h does not include the header file of Class A, but it can be compiled. Why? Because it is not required, only the pointer and reference pointing to a are used in the header file. The C ++ standard does not require the complete information of Class A to define these two variables. When will it be necessary? When a member function or object of a is called by pointer or reference.
//B. cpp//Haha, now I have a complete definition of A. You can use "->", "..", ":" to call its members.# Include"A.h"# Include"B. H"IntB: Calculate (ConstA & A1,ConstA &A2 ){Return(A1.getvalue ()*A2.getvalue ());}
Here we will talk about the complete information of the class through "->", "." or ":" to call any member of the class, or B to inherit from. Some colleagues may wonder: why is it so difficult to include a's header file directly? If you are interested, take a look.This articleHere is a suspense.
In addition to this application, there is another purpose: to solve the circular dependency between classes.
//A.hClassFred {Public: Barney* Foo ();//Error: Unknown symbol 'barnin'};ClassBarney {Public: Fred*Bar ();};
Here, no matter which class is put before, compilation errors will occur. The solution is to add a forward statement at the beginning.
ClassBarney;ClassFred {...};ClassBarney {...};
C ++ FAQI will explain it again. If you are interested, you can check it out.
Article 2:
The separation of C ++ interfaces and implementations from the needs of information hiding
Let's start with a classmate's question on stackoverflow. The prototype of the problem is as follows (for the original problem, see class member privacy and headers in C ++ ):
Portaljacker: "There is a Class A with some common member functions and private data, as shown below ."
ClassA {Public: X getx (); y Gety (); Z Getz ();..Private: X God; y damn; Z it ;};
"But I don't want users using this class to see my private data. What should I do? It may be because it is too difficult for others to laugh at the variable name! Ah, but what is it about them! I tried to put these three member variables into another header file, just like below, but the compiler reports an error-what should I do ?!"
//A.hClassA {Public: X getx (); y Gety (); Z Getz ();};//A. cppClassA {Private: X God; y damn; Z it ;};
When I first saw this problem, I thought it was naive. First, the definition of a class in C ++ must contain all the member functions and variables of the class. Unlike different functions in a namespace, they can be freely distributed in different source files. What if these private members are visible to the caller? They are private, and users cannot directly access them.
However, I am wrong.
This requirement of portaljacker is actually quite reasonable. Imagine that callers generally use Class A in this way.
// main. CPP # include " . h " int main () {A; x = . getx (); y = . gety (); z = . getz ();.. return 0 ;}
Generally, the caller must include the header file of the definition of a before compilation can be successful. That is to say, a compilation dependency is established: Main. cpp-> A. H. In this way, any changes in the. h file will cause the main. cpp to be re-compiled, even if the changes are only private variables in Class A (such as name changes ). This is very bad because the private data of a class belongs to its implementation details (implementation details). Ideally, it should be hidden and its changes are invisible to callers. Oh, I wonder if you have ever encountered such a project, which contains hundreds of source files. You just changed a small header file and found that most files in the project were re-compiled, but they were not compiled in a few minutes.
In fact, portaljacker raised a very good question. The key to the problem is how to hide the implementation details. In this way, the caller will neither see any internal implementation of the class nor be forced to re-compile because of any changes in the implementation.
Before discussing the solution to the problem, it is necessary to look back at why portaljacker's approach does not work. He puts the definitions of the common and private member types of the same class into the definitions of two classes with the same name (see the above ).
I heard it, and you said no. That's right. Why ?" Because the class definition cannot be separated .. "Okay, but why ?" C ++ is like this, common sense! "SeniorProgramMembers may even go to a page of the C ++ standard and say, "Okay, this is the standard." Many of us (including me) know what a book (or teacher) says when learning a language, as long as they know how to use it correctly, few people will think about the reasons behind the rule.
Return to the topic. One of the major reasons why C ++ does not allow division of class definitions is that the size of objects needs to be determined during compilation. Consider the preceding main function. When the class definition is splitCodeCannot be compiled. Because the compiler needs to know the size of object a when compiling "A a", and this information is obtained by viewing the definition of object. At this time, the private member of the class is not in it, and the compiler cannot determine the size of. Note that this problem does not exist in Java, because all Java objects are referenced by default, similar to pointers in C ++, and the object size does not need to be known during compilation.
Separation of interfaces and Implementations
Now let's go back to the problem to be solved:
- Users do not want to see the internal implementation of the class (for example, how many private data are there, what type they are, and what their names are ).
- Except interfaces, changes to any class should not cause the caller to recompile.
The solution to these problems is to properly hide the implementation. For integrity, let's take a look at several common interfaces and implementation separation technologies. Their support for Information Hiding is different, and not all of the above problems can be solved.
1. Use Private Members
Class interfaces are common, and all implementation details are private. This is also the essence of C ++ Object-oriented thinking. Encapsulate all implementationsPrivate, so that when the class changes, the caller does not need to change any code unless the public interface of the class changes. However, such separation is only preliminary, because it may lead to caller duplication.New compilation, even if the common interfaces do not change.
# include " X. h " #include " Y. h " # include " Z. h " class A { /// some interfaces are public Public : X getx (); y Gety (); Z Getz ();.. // partially private private : X God; y damn; Z it ;};
2. Dependency object declaration rather than definition)
In the previous method, classes A and X, Y, and Z are tightly coupled. If Class A uses pointers rather than objects, Class A does not need to include the definitions of X, Y, and Z. A simple Forward Declaration (Forward Declaration) can be used..
//A.hClassX;ClassY;ClassZ;ClassA {Public: X getx (); y Gety (); Z Getz ();..Private: X*God; y*Damn; Z*It ;};
In this way, when X, Y, or Z changes, the caller (main. cpp) of A does not need to re-compile, which can effectively prevent cascading dependencies. In the previous method, if X is changed, all source files containing a. h need to be re-compiled. Note: When declaring a function, you do not need to define the corresponding class even if the parameter or return value of the function has been copied. (In the above example, x, y, Z header file ). It is required only when the function is implemented.
Iii. pimpl Mode
A better way is to "proxy" all the implementation details of a class to the other class, and provide the interface only. The interface is implemented by calling the corresponding functions of the impl class. Scott Meyers said this is"Truly separated interfaces and Implementations".
// Aimpl. h Class Aimpl { Public : X getx (); y Gety (); Z Getz ();.. Private : X; y; Z ;}; // A.h Class X; Class Y; Class Z; Class Aimpl; Class A { Public : // Possible implementation: X getx () {return pimpl-> getx ();} X getx () y Gety () Z Getz ();.. Private : STD: tr1: shared_ptr <Aimpl>Pimpl ;};
Let's take a look at whether this method can meet our two requirements. First, because any implementation details are encapsulated in the aimpl class, it is invisible to the call end. Second, the call end does not need to be re-compiled as long as the interface does not change. Good! Of course, there is no free lunch in the world, and this method also requires a price. The cost is that an additional aimpl class needs to be maintained, and each call to the interface will cause indirect calls to the corresponding aimpl interface. Therefore, if you encounter such a problem, think about the efficiency and Data encapsulation, which is more important to your code.
Iv. Interface Class
The other method that can meet both needs is to use an interface class, that is, an abstract class that does not contain private data. The caller first obtains the pointer to an aconcrete object, and then operates through the interface pointer. The cost of this method is that there may be one more vptr pointing to the virtual table..
//A.hClassA {Public:Virtual~A ();VirtualX getx () =0;VirtualY Gety () =0;VirtualZ Getz () =0;..};ClassAconcrete:PublicA {...};
Summary:
- try to rely on object declarations rather than definitions. Such loose coupling can effectively reduce dependencies during compilation.
- the class implementation can be completely hidden, and the two methods for reducing compilation dependencies are pimpl and interface.