c + + foundation header file cross reference and circular dependency

This paper mainly analyzes the mutual reference of C + + header files and the interdependence with classes

1. C ross reference of C + + header files

If the C + + header files refer to each other, the compilation cannot pass:

// A.cpp
#include "A.h"

int main()
{
    return 0;
}

// A.h
#include "B.h"

// B.h
#include "A.h"

Try to compile and report an error

                 from A.h:1,
                 from B.h:1,
                 from A.h:1,
                 from B.h:1,
                 from A.h:1,
                 ...
                 ...
                 from B.h:1,
                 from A.h:1,
                 from B.h:1,
                 from A.cpp:1:
B.h:1:15: error: #include nested too deeply
 #include "A.h"

This is because in the preprocessing stage, A.h and B.h are nested with each other, resulting in an infinite loop of header files.

Use #ifndef or #pragma once

In order to avoid the same header file being include d multiple times, there are two macro implementation methods in C/C + +: one is #ifndef method; The other is #pragma once.

There is not much difference between compilers that can support these two methods. But there are still some subtle differences between the two
Mode 1:

#ifndef  __SOMEFILE_H__

#define   __SOMEFILE_H__

 ... ... // Declaration and definition statement

#endif

#ifndef can ensure that the same file will not be included more than once, and that two files with exactly the same content will not be included at the same time. You need to pay attention to the differences in different files__ SOMEFILE_H__ Macro names cannot be the same.

Mode 2:

#pragma once

 ... ... // Declaration and definition statement

#Pragma once the compiler guarantees that the same file will not be included multiple times. It cannot handle the situation that the contents of two files are the same. If the same file is in two locations, it will be processed twice. GCC3.4 does not support #pragma once before.

2. Cyclic dependency of classes

#ifndef and #pragma once solve the problem of circular reference of header files, but if there are class interdependencies, new problems will occur in compilation:

// A.cpp
#include "A.h"

int main()
{
    return 0;
}

// A.h
#include "B.h"
class A
{
    B b;
}

// B.h
#include "A.h"
class B
{
    A a;
}

An error is reported during compilation:

In file included from A.h:2:0,
                 from A.cpp:1:
B.h:5:5: error: 'A' does not name a type
     A a;
     ^

Error reason:
In A.h:2, process the statement #include "B.h" to expand the header file
In B.h:5, a member variable of type A is declared in class B, but class A has not been declared at this time
Therefore, the compiler reports an error: 'A' does not name a type '

There are two ways to solve the problem of circular dependency:

1. Use forward declaration
2. Avoid circular reference at the design level

a. Use forward declaration

C + + classes can be declared forward. In this example, class A can be compiled by declaring forward in the B.h file, as follows:

// #include "A.h"
class A;
class B
{
    A* a;
};

Because the class declared forward but not defined is incomplete, class A can only be used to define pointers, references, or pointers and references for function parameters. It cannot be used to define objects or access class members.
This is because the space occupied by class B is determined, but a has not been defined and cannot be determined. A is the determined pointer size,
Therefore, A can be used to define member variables in Class B.

Function of forward declaration:

1. include header files are not required. A large number of imported header files will slow down compilation
2. It can solve the case that two classes call each other circularly

b. Redesign the program structure to avoid circular dependency

Good program design can avoid circular dependency. Refer to the principles of interface isolation and dependency inversion:

Interface isolation principle:
The client should not rely on interfaces that it does not need
Dependencies between classes should be based on the smallest interface
Generally speaking, a single interface is provided for each module

Dependency Inversion Principle:
The upper modules should not depend on the lower modules, they should all rely on abstraction
Abstraction should not depend on details, details should depend on abstraction

When a depends on B and B depends on a, why should class a contain members of class B? Then an interface and an abstract class IB can be abstracted. B is its implementation. Both class A and B depend on class IB, and the circular dependency is eliminated.

Coding example:

// In C + +, if at least one function in a class is declared as a pure virtual function, the class is an abstract class
class IB
{
public:
    virtual int getVal() = 0;
}

class B : public IB
{
public:
    A a;
    int val = 2;
    int getVal(){
        return val;
    }
};

class A
{
public:
    IB *b;
    int getValOfB()
    {
        return b->getVal();
    }
};


int main()
{
    IB *b = new B();
    A a;
    a.b = b;

    cout << a.getValOfB() << endl;   
    return 0;
}

At this time, class A and class B depend on IB, class B depends on Class A, the circular dependency is broken, and class a can still get the data of class B in the form of interface.

Tags: C C++

Posted on Sat, 16 Oct 2021 04:03:24 -0400 by Russia