The difference between a class's predecessor declaration and a containing header file in C + +

Source: Internet
Author: User

First, the class nesting question

C + + header file Duplication is really a headache, suppose we have two classes A and B, respectively, defined in the respective header file A.h and B.h, but in a to use the b,b also use a, but this is certainly wrong:

Class B;

Class a{

Public

b b;
};

Class b{

Public

A;
};

Because in a object to open up a piece of space belonging to B, and B has a space, is a logic error, cannot be achieved, here we just need to change one of the Class B members into a pointer form can avoid this infinite extension of the circle, why change a instead of B? Because even if you do a similar action in B, you will still compile the error, on the surface this is only a sequential problem

Why is that? Because the C + + compiler compiles the source files from top to bottom, the definition of each data always needs to know the size of the defined data type. After declaring the statement Class B, the compiler already knows that B is a class, but the data in it is unknown, so the size of type B is not known, which causes the compilation to fail, and the following compilation error is vc++6.0:

Error C2079: ' B ' uses undefined class ' B '

After changing B in a to the B pointer type, the pointer occupies a certain amount of space on a particular platform (4 bytes on the Win32 platform), which can be compiled

Ii. nesting of classes in different header files

In actual programming, different classes are usually placed in separate header files, so that two classes will have a different problem when referencing each other, and repeating compilation is the root cause of the problem. To ensure that header files are compiled only once, the common approach in C + + is to use conditional compilation commands in the header file we often see the following segment of the statement (in the case of vc++6.0 auto-generated header files):

#IFNDEF Teststr

#define Teststr

Many statements

#endif

This means that if you do not define the macro, then you define it and then execute all the statements until #endif. If the next time you want this code, because the macro has already been defined, so the duplicated code will not be executed again this is really a clever and efficient way in the high version of VC + +, You can also use this command instead of all of the above:

#pragma once

It means that the code in this file is only used once

But do not assume that the use of this mechanism is all done, such as in the following code:

Code in the file A.h

#pragma once

#include "B.h"

Class a{

Public

b* b;

};

Code in the file B.h

#pragma once

#include "A.h"

Class b{

Public

* A;

};

Here both use pointer members, so nesting itself is not a problem, after using the # include "A.h" in front of the main function, the main compilation error is as follows:

Error C2501: ' A ': missing Storage-class or type specifiers

Still the type cannot find the error. In fact, there is still a need for a pre-declaration, add the predecessor declaration, respectively, can be compiled successfully. The code form is as follows:

Code in the file A.h

#pragma once

#include "B.h"

Class B;

Class a{

Public

b* b;

};

Code in the file B.h

#pragma once

#include "A.h"

Class A;

Class b{

Public

* A;

};

This can at least indicate that the header file contains not replace the predecessor declaration, sometimes only rely on the pre-declaration to solve the problem, we also have to think about, with the predecessor declaration when the header file contains or necessary? We tried to get rid of the # include lines in A.h and B.h, and found that there was no new error. When do we need a pre-declaration, when do I need a header file to contain it?

Three, two points principle

Header file contains is actually a thought very cumbersome work, not only we look tired, compiler compile time is also very tired, plus the head file often appear in the macro definition feel a variety of macro definition of the expansion is very time-consuming, far less than the speed of the custom function I just put forward the two-point principle of the sentence structure between different header file source files. For reference only:

The first principle: if you can not include the header file, then do not include, this time the pre-declaration can solve the problem, if the use of only a pointer to a class, not the specific object of the class (non-pointer), and do not have access to the specific members of the class, then the pre-declaration can be, Because the size of the data type of the pointer is specific, the compiler can be informed.

The second principle: try to include the header file in the CPP file instead of the header file. Suppose that a member of Class A is a pointer to Class B and uses the predecessor Declaration of Class B in the header file of Class A, then in the implementation of a we need to access the specific members of B, so we need to include the header file, then we should include the header file of Class B instead of the declaration part (H file) in the implementation part of Class A

Iv. Pre-declaration of C + +

People who are just beginning to learn C + + will encounter this problem:

Defines a class A, which uses object B of Class B and then defines a Class B, which also contains an object A of Class A, which is the case:

    1. A.h
    2. #include "B.h"
    3. Class A
    4. {
    5. ....
    6. Private
    7. b b;
    8. };
    9. B.h
    10. #include "A.h"
    11. Class B
    12. {
    13. ....
    14. Private
    15. A;
    16. };

A compilation, there is an inter-inclusion problem, then someone out of the way, the solution to this problem can be declared in the A.h file Class B, and then use a pointer b.

  1. A.h
  2. #include "B.h"
  3. Class B;
  4. Class A
  5. {
  6. ....
  7. Private
  8. b b;
  9. };
  10. B.h
  11. #include "A.h"
  12. Class B
  13. {
  14. ....
  15. Private
  16. A;
  17. };

Then the problem is solved.

However, some people know why the problem is solved, that is to say, add a forward statement why the problem solved. Now let me look at this predecessor statement.

There are many benefits to the predecessor declaration of a class.

One of the benefits of using a predecessor statement is that when we modify Class B with the predecessor Declaration of Class B, we only need to recompile class B and not need to recompile a.h (of course, when you really use Class B, you must include B.h).

Another benefit is to reduce the size of Class A, the above code is not reflected, then we look at:

  1. A.h
  2. Class B;
  3. Class A
  4. {
  5. ....
  6. Private
  7. B *b;
  8. ....
  9. };
  10. B.h
  11. Class B
  12. {
  13. ....
  14. Private
  15. int A;
  16. int b;
  17. int C;
  18. };

We look at the code above, the size of Class B is 12 (on a 32-bit machine).

If we include the object of B in Class A, then the size of Class A is 12 (assuming there are no other member variables and virtual functions). If you include a pointer to class B *b variable, then the size of Class A is 4, so it is possible to reduce the size of Class A, especially when the STL container contains the object of the class rather than the pointer, this is particularly useful.

In the case of a predecessor declaration, we can only use pointers and references to the class (because the reference is also the implementation of the pointer).

So, let me ask you a question, why do we use only type pointers and references when we have a predecessor declaration?

If you answer: that is because the pointer is a fixed size and can represent any type, then you can give you 80 points. Why only 80 points, because not yet fully answered.

To get a more detailed answer, let's look at the following class:

  1. Class A
  2. {
  3. Public
  4. A (int a): _a (a), _b (_a) {} //_b is new add
  5. int get_a () const {return _a;}
  6. int Get_b () const {return _b;} //New add
  7. Private
  8. int _b; //New Add
  9. int _a;
  10. };

Let's look at the class A defined above, where the _b variable and the Get_b () function are added to this class.

So I ask you, after adding the _b variable and the Get_b () member function, what's changed in this class, think about it and answer it again.

Well, let's make a list of these changes:

The first change of course is the addition of the _b variable and the Get_b () member function;

The second change is the size of this class, which is 4 and now 8.

The third change is that the offset address of the member _a has changed, the original offset from the class is 0, and now it is 4.

The changes above are the explicit and visible changes we have. There is a hidden change, think about what is ...

This hidden change is the default constructor for Class A and the default copy constructor has changed.

As you can see from the above changes, the behavior of any member variable or member function that calls Class A needs to be changed, so our a.h needs to be recompiled.

If our b.h is like this:

    1. B.h
    2. #include "A.h"
    3. Class B
    4. {
    5. ...
    6. Private
    7. A;
    8. };

Then our b.h needs to be recompiled, too.

If this is the case:

    1. B.h
    2. Class A;
    3. Class B
    4. {
    5. ...
    6. Private
    7. A *a;
    8. };

Then our b.h will not need to be recompiled.

Like us, the predecessor declares the Class A:

Class A;

is an incomplete declaration, as long as there is no action in class B that requires an understanding of the size or members of Class A, such incomplete declarations allow the declaration of pointers and references to a.

And the statement in the previous code

A;

It is necessary to understand the size of a, otherwise it is impossible to know if the class B allocates memory size, so the incomplete predecessor declaration will not work, you must include A.H to obtain the size of Class A, but also to recompile Class B.

To go back to the previous question, one reason for using a predecessor declaration to allow only the declaration to be a pointer or reference is if the declaration does not have to be performed to understand the size of class A or the operation of a member, so declaring a pointer or reference is not an operation that needs to know the size or the members of Class A.

This article is largely inspired by the fourth chapter Compiler firewalls and the Pimpl Idiom (compiler firewall and Pimpl idioms) in exceptional C + + (HURB99), which describes the implications of reducing compile-time dependencies and a Some idiomatic methods, in fact, the most common and no side effect is the use of the predecessor declaration to replace the inclusion of the header file.
Item 26 of guideline-"Never #include a header when a forward declaration would suffice"

Here, I have summed up the use of the predecessor declaration can be used to replace the various cases including the header file and give some sample code.
First, why should we include header files? The answer to the question is simple, usually we need to get a definition of a type. So the next question is, under what circumstances do we need the definition of a type, and under what circumstances should we just declare it enough? The answer to the question is that when we need to know the size of this type or need to know its function signature, we need to get its definition.
Suppose we have type A and type C, in which case the definition of C needs to be:

    1. A inherits to C
    2. A has a member variable of type C
    3. A has a member variable of a pointer of type C
    4. A has a member variable with a reference of type C
    5. A has a member variable of type std::list<c>
    6. A has a function whose signature parameters and return values are of type C
    7. A has a function whose signature parameter and return value are Type C, which calls a function of C, and the code in the header file
    8. A has a function whose signature parameters and return values are type C (including type C itself, C's reference type and C's pointer type), and it calls another function that uses C, and the code is written directly in a header file
    9. C and A are inside the same namespace.
    10. C and a are in different namespaces.

1, there is no way to get the definition of C, because we have to know the member variables of C, member functions.
2, we need the definition of C, because we want to know the size of C to determine the size of a, but you can use PIMPL usage to improve this point, details please
Look at Hurb's exceptional C + +.
3,4, do not need, pre-declaration can be, in fact, 3 and 4 is the same, the reference is physically a pointer, its size depending on the platform, it may be 32 or 64 bits, anyway, we do not need to know the definition of C can determine the size of this member variable.
5, not required, there might be an old-fashioned compiler needed. The container inside the standard library is like List, Vector,map,
When you include a member variable of a list<c>,vector<c>,map<c, c> type, you do not need the definition of C. Because they are in fact the use of C pointers as member variables, their size is fixed at the beginning, and will not vary according to the template parameters.
6, no need, as long as we do not use to C.
7, we need to know the signature of the calling function.
8,8 The situation is more complex, directly see the code will be more clear.

c& Dotoc (c&);
c& doToC2 (c& C){return Dotoc (c);};

From the above code, a member of the function doToC2 called another member function Dotoc, but whether it is doToC2, or DOTOC, their parameters and return type is actually a reference to C (as a pointer, the case is the same), the reference assignment is the same as the value of the pointer, Is nothing more than the assignment of the shape, so here does not need to know the size of C and no call C any function, in fact, there is no need for the definition of C.
However, let's change one of the c& to C, such as the following examples:

1.
c& Dotoc (c&);
c& doToC2 (c c){return Dotoc (c);};

2.
c& Dotoc (C);
c& doToC2 (c& c) {return Dotoc (c);};

3.
C Dotoc (c&);
c& doToC2 (c& c) {return Dotoc (c);};

4.
c& Dotoc (c&);
C doToC2 (c& c) {return Dotoc (c);};

Either way, it implicitly contains a call to a copy constructor, such as 1, where parameter C is generated by the copy constructor, and the return value of the DOTOC in 3 is an anonymous object generated by the copy constructor. Because we call the copy constructor of C, we need to know the definition of C in either case.

9 and 10 are the same, we do not need to know the definition of C, only 10 of the case, the syntax of the predecessor declaration is slightly more complex.
Finally give a complete example, we can see in two different namespaces of type A and c,a is how to use the predecessor declaration to replace the header file directly including C:
A.h

#pragma once
#include <list>
#include <vector>
#include <map>
#include <utility>
Pre-declaration of different namespaces
Namespace Test1
{
Class C;
}

Namespace Test2
{
Use using to avoid fully qualified names
Using Test1::c;

Class A
{
Public
C UseC (c);
c& Dotoc (c&);
c& doToC2 (c& C){return Dotoc (c);};

Private
Std::list<c> _list;
Std::vector<c> _vector;
Std::map<c, c> _map;
c* _pc;
c& _RC;

};
}


E..

#ifndef C_h
#define C_h
#include <iostream>

Namespace Test1
{

Class C
{
Public
void print () {std::cout<< "Class C" <<STD::ENDL;}
};

}

#endif//C_h

The difference between a class's predecessor declaration and a containing header file in C + +

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.