Detailed explanation of C++ preposition declaration

  • 2020-11-20 06:10:46
  • OfStack

Predeclaration is a common technique used in C/C++ development and is mainly used in three situations:

Variable/constant, for example extern int var1 ;; Functions, such as void foo(); , note that the member functions of the class cannot be predeclared separately; Class, such as class Foo; , you can also predeclare template classes: template class<typename T1, int SIZE>Foo; . If the class is contained in a namespace, you need to make a predeclaration within the namespace: namespace tlanyan {class Foo;}; And not like this: class tlanyan::Foo; .

Predeclaration effect

According to its purpose, the main functions of a predeclaration are:

Avoid repeatedly defining variables; Avoid introducing function definition/declaration files so that function files do not recompile dependent files when they change; Solve the problem of circular dependencies.

The first two are easy to understand, while the third is a little more complex, but is the most important use for pre-declaration. It solves the dependency problem of class A containing class B and class B containing class A. Cyclic dependency 1 is generally a design problem, which can be solved by means of interface, introduction of auxiliary classes, etc. Pre-declarations can also be solved, albeit architecturally.

Whether or not A and B are defined in the same file, c++ can never resolve circular dependencies in the following form (for reasons explained later) :


// file: A.hpp
#include "B.hpp"
class A {
 int id;
 B b;
};

// file: B.hpp
#include "A.hpp"
class B {
 ...
 A a;
};

To solve this problem, the predeclaration needs to be converted to another form in conjunction with the pointer. The main points are as follows:

Convert at least some type of variable to a pointer, such as A to B *; Use a predeclaration for B in class A; Class A's definition file removes the inclusion of the class B file (with inclusion protection ignored).

With the use of pre-declarations, here is one possible solution (both classes use pre-declarations) :


// file: A.hpp
//3.  Remove the B Contains (used by #pragma once or #ifndef B_HPP Such protection measures are not necessary.) 

// 2.  Predeclared class B
class B;
class A {
 int id;
 // 1.  Member variables are converted to Pointers 
 B* b;
};

// file: B.hpp
// 3.  Remove the A Containing (containing protection is not necessary) 

// 2.  Predeclared class A
class B {
 ...
 // 1.  Member variables are converted to Pointers 
 A* a;
};

In-depth pre-declaration

If you have experience with other programming languages, c++ is a bit weird: languages like Java/C#/Python/PHP can easily loop references without using similar predeclaration techniques. This begs the question: Why does C++ have to be resolved with a pre-declaration?

The reason is that there are two ways for C++ to define objects: one is the form of A a, which is an object, which is used to call member variables or functions. The other is A* a, where a is a pointer and calls a member variable or function with - > , which points to the address to store the actual object, which is allocated in the heap.

We need to know the exact memory size to allocate objects, but we cannot determine the size of A and B objects in the following form:


class A {
  B b;
};
class B {
  A a;
};

For this simple example, you can intuitively assume that A and B use the same amount of memory, such as 1 byte, but can also be 2 bytes, 3 bytes, etc. According to the memory alignment requirements, 1 is usually 4 bytes, 8 bytes, etc. In either case, the compiler is unable to determine the memory footprint of its object, and an error is reported to stop compilation. So you know why C++ should never (can't) do this?

So why does a combination of predeclarations and Pointers solve the problem of circular references? This is because data type Pointers normally occupy the same amount of memory as compilers on the same 1 machine. Pointer 1 is usually 4 or 8 bytes, corresponding to 32 and 64 bit Pointers. With Pointers, the class size can be easily determined even with circular references. This is why Java/C#/Python/PHP and others can easily iterate over references: in these languages, object variables are actually Pointers, which means object variables are passed by reference.

If you don't remove the mutual inclusion of files, can you omit the predeclaration? The answer is no, for the following reasons:

C++ is compiled according to 1 compilation unit (translation unit). If two files contain each other and no protection measures are included, such as #pragma once, then a recursive inclusion will occur and the compiler will report an error. If both header files have file inclusion protection, B is included when compiling A, but because B contains A, the inclusion protection in A takes effect, so the contents of the B file are not actually introduced into A, an error reporting B as an unknown symbol.

In general, pre-declaration is a must, whether or not you remove the other's header file. In practice, it is a good practice to remove unnecessary headers in order to avoid the cost of recompiling when files change.

The above is a detailed explanation of C++ pre-declaration details, more information about C++ pre-declaration please pay attention to other related articles on this site!


Related articles: