Avoiding Circular Dependencies of header files [duplicate]
Asked Answered
H

8

65

Do you have any good advice on how to avoid circular dependencies of header files, please?

Of course, from the beginning, I try to design the project as transparent as possible. However, as more and more features and classes are added, and the project gets less transparent, circular dependencies start happening.

Are there any general, verified, and working rules? Thanks.

Holsinger answered 27/1, 2011 at 13:8 Comment(0)
K
63

If you have circular dependency then you doing something wrong.

As for example:

foo.h
-----
class foo {
public:
   bar b;
};

bar.h
-----
class bar {
public:
   foo f;
};

Is illegal you probably want:

foo.h
-----
class bar; // forward declaration
class foo {
   ...
   bar *b;
   ...
};

bar.h
-----
class foo; // forward declaration
class bar {
   ...
   foo *f;
   ...
};

And this is ok.

General rules:

  1. Make sure each header can be included on its own.
  2. If you can use forward declarations use them!
Kathrynkathryne answered 27/1, 2011 at 13:13 Comment(9)
+1 Hi Artyom, thanks for the reply. more frequent usage of forward declarations might be helpful.Holsinger
@Artyom: if the pointer is meant to own the resource, I'd advise using a scoped_ptr or unique_ptr. If the pointer is merely a reference to an object, then it could be necessary to use an Observer pattern so that it's "unset" whenever the referenced object is destroyed.Wharfinger
@Matthieu M. Of course, (or auto_ptr which is better when you don't want to be dependent on boost or C++0x). But I rather wanted to show general idea rather live code. It may even be std::vector<foo> which would work if foo has forward declaration.Kathrynkathryne
@Artyom: no, don't use auto_ptr, it's worse. scrap off the code of scoped_ptr from boost if you need to, but auto_ptr brings too many surprises (on copy / assign).Wharfinger
@Matthieu if you work with current C++ standard it is only smart pointer there and it works as scoped_ptr most of the time (of course you should be aware of its move semantics that is very nice when you know how to use it).Kathrynkathryne
@Artyom: I know, but boost license is very permissive so you can (mostly) just rip the scoped_ptr out of boost (or even reimplement it yourself, it's quite easy... just beware of exceptions ^^) and you'll have a noncopyable smart pointer.Wharfinger
@Matthieu Nothing wrong with auto_ptr it was fine and it is sill fine. And in some cases it is very good to define move policy like returning auto_ptr from function. It was deprecated in flavor of unique_ptr only because C++0x ans rvalue reference which does not exists in C++98 and allows to do things safer. Also please note, the standard had not accepted scoped_ptr in it.Kathrynkathryne
@Artyom: I've never liked auto_ptr because of its screwed concept of "copy", which is usually surprising... I know that we "needed" it somewhat for returning pointers from factory, but whenever a copy is not necessary, or not wanted. scoped_ptr is better in this regard, for C++03; it's also completely unnecessary once you get a unique_ptr :)Wharfinger
But if we are using some methods of the *b pointer ? Then we cannot forward include it. What to do then ? Here is showed that we can inline these function cplusplus.com/forum/articles/10627 but it doesn't look like a good general approachHarvestman
A
22
  • Use forward declarations where possible.
  • Move any header includes out of a header file and into the corresponding cpp file if they are only needed by the cpp file. Easiest way to enforce this is to make the #include "myclass.h" the first include in myclass.cpp.
  • Introducing interfaces at the point of interaction between separate classes can help reduce dependencies.
Austral answered 27/1, 2011 at 13:41 Comment(3)
+1 Hello Jon, thanks for your reply. Some your advices were already mentioned above, but the one to always #include heade files into .cpp files instead .h files was new and helpful.Holsinger
I think this answer better addresses the question about how to avoid compilation errors with circular dependencies while avoiding the mantra that you did something wrong because you have to deal with a circular dependency. If you're working with GoF design patterns and complexity you WILL have a circular dependency at some point. The best advice is not just forward declaration (that oversimplifies the solution), but bullet point #2.Ashe
Second suggestion is what I was looking forChesty
A
11

Some best practices I follow to avoid circular dependencies are,

  1. Stick to OOAD principles. Don't include a header file, unless the class included is in composition relationship with the current class. Use forward declaration instead.
  2. Design abstract classes to act as interfaces for two classes. Make the interaction of the classes through that interface.
Advowson answered 27/1, 2011 at 14:0 Comment(1)
+1 hey Arun, especially the second advice to use abstract/interface classes was helpful. I will give it a try. Thanks.Holsinger
S
9

A general approach is to factor out the commonalities into a third header file which is then referenced by the two original header files.

See also Circular Dependency Best Practice

Salientian answered 27/1, 2011 at 13:10 Comment(2)
+1 Hi Ed, that is another very good advice. Thanks.Holsinger
I checked the link you provided, and it shows nice example of redesigning classed to avoid circular dependancies.Holsinger
O
4

depending on your preprocessor capabilities:

#pragma once

or

#ifndef MY_HEADER_H
#define MY_HEADER_H
your header file
#endif

If you find it very boring to design header files maybe makeheaders from Hwaci (designers of SQLite and fossil DVCS) could be of interest for you.

Orth answered 27/1, 2011 at 13:10 Comment(2)
This is not so much to avoid circular dependencies, as to avoid "redefinition of symbol" errors. It is a standard, absolutely needed practice nevertheless.Freedom
Hello Benoid, yeah, I have to agree with Peter Torok. This something explained in every textbook and in a must-use pratcice. Thank you very much, for your response.Holsinger
L
4

What you're aiming at is a layered approach. You can define layers where modules can depend on lower layer modules but the inverse should be done with observers. Now you can still define how fine-grained your layers should be and whether you accept circular dependency within layers, but in this case I would use this.

Loar answered 27/1, 2011 at 13:40 Comment(2)
+1 hello Stefaanv, the layered approach is quite new to me, and looks like something that requires a lot of preparations and redesign. It is very valuable advice. Thank you.Holsinger
The layered approach is a great idea, notably because it's not C++ specific and is therefore valuable in a lot of situations :)Wharfinger
C
3

In general header files should forwardly declare rather than include other headers wherever possible.

Also ensure you stick to one class per header.

Then you almost certainly will not go wrong.

The worst coupling usually comes from bloated template code. Because you have to include the definition inside the header, it often leads to all kinds headers having to be included, and then the class that uses the template includes the template header, including a load of other stuff.

For this reason, I would generally say: be careful with templates! Ideally a template should not have to include anything in its implementation code.

Cornute answered 27/1, 2011 at 13:58 Comment(1)
+1 Hi CashCow, to be honest, I did not pay too much attention to forward declarations. Instead I used #include. Thank you very much, for this answer.Holsinger
H
2

Altough Artyom provided best answer this tutorial is also great and provides some extenstions http://www.cplusplus.com/forum/articles/10627/

Harvestman answered 17/4, 2013 at 10:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.