📅 2010-Jul-22 ⬩ ✍️ Ashwin Nanjappa ⬩ 🏷️ cpp, dependencies ⬩ 📚 Archive
Here are some guidelines from experience that I have found useful to handle or fix cyclic dependencies in C++ code. These can be used on new code or to polish ugly old code back to a shiny state.
Remove all global variables, functions, constants and enums. Convert them to static methods, variables and constants in already existing classes or create new utility classes for them. Enums should be moved into classes too. Now you have only classes to deal with, there are no other kinds of entities at the source file level.
Try to place only one class declaration and its definition in the corresponding header and source file. If you feel that this granularity is too small, you could place a couple of related classes per header and source file. But, the finer granularity you can achieve the better it is for code maintenance and compile times.
Examine all your classes and form a class graph. Do not use the class hierarchies or graphs from your design documents. Those give a high level picture, while what we want is the physical dependencies between the classes in our code. That is, the dependencies in your code files that your C++ compiler has to grapple with when it tries to compile them. Here are some simple steps that resulted in the graph shown above:
// Triangle.h
class Triangle : public Facet
/***/ }; {
Class Triangle extends from class Facet, so Triangle depends on Facet. Draw an arrow from Triangle to Facet.
* ```cpp
// Triangle.h class Triangle { Point p[3]; };
Class Triangle has a member of class Point, so Triangle depends on Point. Draw an arrow from Triangle to Point.
* ```cpp
// Quad.h
class Quad
{
Point p[4];
bool checkForTriangle( const Triangle& ) const;
Triangle getTriangle( int ) const;
};
Class Quad has a method which has a parameter of class Triangle. It also has a method which returns a Triangle. Neither of this implies that Quad depends on Triangle.
The result should be a directed acyclic graph. If the graph has cycles, then you have a cyclic dependency. Look at the cycle and decide how to break it at one of its classes. Most of the time, the mere act of creating a detailed graph of dependencies is enough to get a clue to break the cycle. Re-examine the classes on the cycle to see if the parent class object can be passed in as a parameter to a few of the methods. Or in drastic cases, see if you need to create another class that extends or has members from the problematic classes.
Something that helps drastically is reducing the number of header inclusions by using forward declarations. One needs to be very strict with the header inclusion in header files. (See this post on header inclusion convention.) Comparatively, one could be a bit more relaxed on the header inclusion in source files. This is because source files are not included by other files, and thus are like leaves in the graph. For example, cleaning up the header inclusion in the above header files:
Triangle.h should include Facet.h.
Triangle.h should also include Point.h.
Quad.h should not include Triangle.h. Instead, it should only include a_ forward class declaration_ of Triangle. This may seem frivolous since an inclusion of Triangle.h would work fine here. But, this clean habit helps in the long run. Unnecessarily including Triangle.h would bring in all the names it has declared inside it and the entire tree of header files that it has included! Thus, preventing inclusion of header files nips this cancer in the bud, and also helps cut down on compile times quite drastically.cpp // Quad.h #include "Triangle.h" // WRONG! class Triangle; // CORRECT class Quad { bool checkForTriangle( const Triangle& ) const; Triangle getTriangle( int ) const; };
Cyclic dependencies can be extremely hairy. Hopefully, these guidelines will help you fix them. 😊