2010/10/28

[C++] Forward Declaration for Circular Dependency Problem

0. Keywords

Cyclic Dependency, Circular Dependency, Cross Dependency, Forward Declaration.

1. Cyclic Dependency (Circular Dependency, Cross Dependency) Problem

Two classes depending on each other would cause Cyclic Dependency (Circular Dependency, Cross Dependency). For example, class A (in A.h) is associated with class B in (B.h) and vice versa.

main.cpp

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

int main() {return 0;}

 

A.h

#ifndef _A_H_
#define _A_H_

#include "B.h"

class A{
    B* b; // A depends on B
};

#endif

 

B.h

#ifndef _B_H_
#define _B_H_

#include "A.h"

class B{
    A* a;  // B depends on A

};

#endif

Let's see what would happen by tracing the code at main.cpp. 

At frist, the statement #include "A.h" is placed. Preprocessor go to A.h, finding out that _A_H_ is not yet defined (#ifndef _A_H_), so preprocessor define _A_H_ (#define _A_H_ ). After that, it encounter #include "B.h".

Then preprocessor go to B.h, finding out that _B_H_ is not yet defined (#ifndef _B_H_), so preprocessor define _B_H_ (#define _B_H_ ). After that, it encounter #include "A.h". However, A.h will not be included in B.h again because _A_H_ is already defined now.

Note that class B have no idea about A. Therefore, in the the declararion of class B in B.H, declararing A* a; will cause error since A is not a class, a struct, or a primitive type in the view of class B. The error message would be something like:

error C4430: miss type - C++ does not support default-int.

 

2. solution

The Circular Dependency Problem can be solved by Forward Declaration of class A such that class B would know A is a class.

B.h

#ifndef _B_H_
#define _B_H_

#include "A.h"

class A;

class B{
    A* a;
};

#endif

 

3. Incomplete Type Problem for Forward Declaration

Before a type's  definition is seen, this type is called incomplete type. The statement

class A; in B.h in the code above tells that A is a class without  definition. In this case, A is an incomplete type.

It is not possible use an incomplete type to define a variable or class member. An incomplete type may be used to define only pointers or references to the type or to declare (but not define) functions that use the type as a paremeter or return type.[1]

In the following code, we observe that the statement a->doSomething(); in B.h causes a error because class B knows A is a class only and the definition of doSomething(); of class A has not yet been seen.

Moreover, after we delete a->doSomething(); in B.h, no error messages would show. But why b->doSomething(); in A.h works fine? It because the class A know everything about class B, including the definition of B. The class B is a complete type in the view of class A.

main.cpp

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

int main() {return 0;}

A.h

#ifndef _A_H_
#define _A_H_

#include "B.h"

class B;

class A{
public:   
    B* b; 
    void doSomething(){}
    A(){
        b->doSomething(); // ok
    }

};

#endif

B.h

#ifndef _B_H_
#define _B_H_

#include "A.h"

class A;

class B{
public:
    A* a;
    void doSomething(){}
    B(){
        a->doSomething(); // error
    }
};

#endif

Similarly, we know the fact that an incomplete type can not define a variable or class member. Thus ,the following code causes a error when defining an incomplete class member (The statement A a; in class B)

A.h

#ifndef _A_H_
#define _A_H_

#include "B.h"

class B;

class A{ 
    B b; // ok
};

#endif

B.h

#ifndef _B_H_
#define _B_H_

#include "A.h"

class A;

class B{
    A a; // error
};

#endif

Reference

[1] By Stanley B. Lippman, Josée Lajoie, Barbara E. Moo, "C++ Primer, Fourth Edition", Publisher: Addison Wesley Professional, Pub Date: February 14, 2005, ISBN: 0-201-72148-1.

[2] Forward declaration From Wikipedia.

[3] Circular dependency From Wikipedia.

More posts regarding [C++]:

  1. implicit constructor
  2. 使用Pointer to pointer或Reference to pointer,改變pointer的address
  3. Dynamic Memory Allocation in C and C++
  4. [C++] Call by value、Call by pointer、Call by reference
  5. (C++)virtual與pure virtual

沒有留言:

2024年React state management趨勢

輕量化 在過去Redux 是 React 狀態管理的首選函式庫。 Redux 提供了強大的功能和靈活性,但也帶來了一定的學習成本和複雜度。 隨著 React 生態的不斷發展,越來越多的開發者開始追求輕量化的狀態管理函式庫。 Zustand 和 Recoil 等庫以其簡單易用、性...