C++ Circular Dependency

From Schmid.wiki
Jump to: navigation, search
Auryn

Contents

Example

Imagine a spaceship with a weapon, where the weapon is a separate class, and the weapon must know its owner:

class Ship {
    Weapon &weapon;
};

class Weapon {
    Ship &owner;
};

This does not work, as the Weapon class is undefined when defining class Ship. The remedy is a forward declaration:

class Weapon; // forward definition

class Ship {
...

Discussion

The above approach can get very tricky in larger class hierarchies. Imagine the example split into files, ship.h:

#pragma once
#include "weapon.h"
class Weapon;                       // forward declaration
class Ship {
public:
    Ship() : weapon(0) {}
    void fire() { weapon->fire(); } // Error: we are *not* allowed to use
                                    // methods of the undefined class.
    Weapon *weapon;                 // We may have a pointer to an undefined
};                                  // class, but not a reference.

weapon.h:

#pragma once
#include "ship.h"
class Ship;                         // forward declaration
class Weapon {
public:
    Weapon(Ship &owner) : owner(owner), fired(0) {}
    void fire() { fired++; }
    Ship &owner;
    int fired;
};

and main.cpp:

#include "weapon.h"
#include "ship.h"
int main(void) {
    Ship R9a;
    Weapon laser(R9a);
    R9a.weapon = &laser;
}

Note that although we would like to initialize the Ship with a Weapon reference, it is impossible to retain the initialization of the Weapon class with the Ship reference. We cannot have both. Therefore, we opt to use a weapon pointer which is set after creating both the Ship and the Weapon. If we forget to set it, the program will fail, so it should be changed to something similar to:

void fire() { if(weapon) weapon->fire(); }

However, the above example doesn't even compile. The problem is that Ship::fire() uses Weapon::fire() before Weapon is defined. We may not access members of an as yet undefined class.

The solution is unsatisfying: we cannot retain Ship::fire() as an inline member function. We have to edit ship.h:

...
class Ship {
public:
    Ship() : weapon(0) {}
    void fire();          // May not be inline, as it accesses
                          // a weapon member.
    Weapon *weapon;
};

add put the Ship::fire() implementation in a separate translation unit (ship.cpp file):

#include "ship.h"
void Ship::fire() { weapon->fire(); }

Conclusion

  • Try to avoid circular dependencies in your design, as the C++ implementation may get hairy.
  • Use forward declarations to allow circular dependencies if deemed necessary.
  • As we cannot assume anything about a forward declared class, we cannot access its members in inline functions. Move function definitions accessing forward declared class members to a separate translation unit (.cpp file).

References

Personal tools