-
August 30th, 2019, 01:10 PM
#1
[RESOLVED] Will this header only strategy, avoid circular inclusion?
I know the basic rules about header-only libraries (such as functions must be declared inline, etc..) but when I tried creating a complicated engine, I was encountering circular inclusion problems. The reason was that I was defining and implementing each class in each own .h file. So if I had two classes which both of them needed to include and use the functionallities of the other, I had circular inclusion.
This is the solution I thought of:
1) For each class, I'm creating two files, a .h and a .hpp file (both of them are header files).
2) The .h files contain only the specification of the class and forward declarations to other classes (I have to heap allocate them). The rule is to not include any other header files inside .h files, only standard libraries.
3) The .hpp files include their corresponding .h file and implement the functionalities.
4) .hpp's are included only once by the client with the help of an initialization header file (which include all the .hpp files only once).
I believe this will solve the circular inclusion problem.
An Example:
a.h
Code:
#ifndef A_h
#define A_h
#include <iostream>
class B;
class A
{
public:
A();
void whatssup();
inline setB(B *b) {this->b = b;}
private:
B *b;
};
#endif
a.hpp
Code:
#ifndef A_hpp
#define A_hpp
#include "a.h"
#include "b.h"
A::A()
{
}
A::whatssup()
{
std::cout << "whatssup" << std::endl;
}
#endif
b.h
Code:
#ifndef B_h
#define B_h
class A;
class B
{
public:
B(A *a);
private:
A *a;
};
#endif
b.hpp
Code:
#ifndef B_hpp
#define B_hpp
#include "b.h"
#include "a.h"
B::B(A *a)
: a(a)
{
a->whatssup();
}
#endif
Engine.h
Code:
#ifndef engine_h
#define engine_h
#include "a.hpp"
#include "b.hpp"
#endif
Client.cpp:
Code:
#include <Engine.h>
A *a = new A();
B *b = new B(a);
a->setB(b);
So what is your thought? Let me know.
Last edited by babaliaris; August 30th, 2019 at 01:15 PM.
-
August 30th, 2019, 02:42 PM
#2
Re: Will this header only strategy, avoid circular inclusion?
Well unless you're dealing with templates you shouldn't have executable code inside header files. Definitions belong in source files not #include files.
By the way did you try compiling that code?
-
August 30th, 2019, 02:51 PM
#3
Re: Will this header only strategy, avoid circular inclusion?
Originally Posted by jlb1
By the way did you try compiling that code?
No. But I've done something similar and much more complicated and works pretty good.
Originally Posted by jlb1
Well unless you're dealing with templates you shouldn't have executable code inside header files. Definitions belong in source files not #include files.
Well, probably you don't know about header-only libraries. This has some really good advantages when It comes to distributing a library that is ment to be used by others.
-
August 30th, 2019, 03:44 PM
#4
Re: Will this header only strategy, avoid circular inclusion?
Originally Posted by babaliaris
So what is your thought?
Circular inclusion problems can often be resolved by a design change because they indicate tight coupling (like you have between the A and B classes). A first step in my view is to get rid of the coupling if possible because it both improves the design and removes the circular inclusion.
Maybe A and B conceptually work better as one class AB?
Maybe it's possible to change A and B so they no longer depend on each other but on another third class C (that is independent of both A and B)?
If no one of these options are possible you can always keep both A and B on the same include file. If they strongly depend on each other why not keep them close together?
Finally you declare one method in A inline,
Code:
inline setB(B *b) {this->b = b;}
It's not necessary to do that, it will be inlined automatically. It's only free functions you need to declare inline in a header-only situation.
Last edited by wolle; August 31st, 2019 at 02:37 AM.
-
August 30th, 2019, 03:50 PM
#5
Re: Will this header only strategy, avoid circular inclusion?
Originally Posted by jlb1
Well unless you're dealing with templates you shouldn't have executable code inside header files. Definitions belong in source files not #include files.
Who says that? The C++ standard doesn't. It's just an old convention that makes little sense today.
-
August 31st, 2019, 03:42 AM
#6
Re: Will this header only strategy, avoid circular inclusion?
Originally Posted by wolle
Who says that? The C++ standard doesn't. It's just an old convention that makes little sense today.
Agreed. There is no language reason why you can't have executable code (or anything else for that matter) within header files. Also, these shouldn't really be called header files but include files. #include can be used anywhere in the code - not just at the top of a program and can contain any text that is wished to be included at the point of the #include statement. Also, the referenced file can have any extension - not just .h/.hpp (or .c or .cpp) - but .xyz if you want!
All advice is offered in good faith only. All my code is tested (unless stated explicitly otherwise) with the latest version of Microsoft Visual Studio (using the supported features of the latest standard) and is offered as examples only - not as production quality. I cannot offer advice regarding any other c/c++ compiler/IDE or incompatibilities with VS. You are ultimately responsible for the effects of your programs and the integrity of the machines they run on. Anything I post, code snippets, advice, etc is licensed as Public Domain https://creativecommons.org/publicdomain/zero/1.0/ and can be used without reference or acknowledgement. Also note that I only provide advice and guidance via the forums - and not via private messages!
C++23 Compiler: Microsoft VS2022 (17.6.5)
-
August 31st, 2019, 05:52 AM
#7
Re: Will this header only strategy, avoid circular inclusion?
Originally Posted by wolle
Circular inclusion problems can often be resolved by a design change because they indicate tight coupling (like you have between the A and B classes). A first step in my view is to get rid of the coupling if possible because it both improves the design and removes the circular inclusion.
Maybe A and B conceptually work better as one class AB?
Maybe it's possible to change A and B so they no longer depend on each other but on another third class C (that is independent of both A and B)?
If no one of these options are possible you can always keep both A and B on the same include file. If they strongly depend on each other why not keep them close together?
Finally you declare one method in A inline,
Code:
inline setB(B *b) {this->b = b;}
It's not necessary to do that, it will be inlined automatically. It's only free functions you need to declare inline in a header-only situation.
These was some really good advices! I never thought of them. I will keep in mind creating a 3d middle class that might help solving circular inclusion.
The situation where you define them inside the same file, sounds really good! But this needs some good planning before coding, because you need to know the relations between classses. If you instantly dive into coding, you never know what relations might come up in the future.
Last edited by babaliaris; August 31st, 2019 at 05:54 AM.
-
August 31st, 2019, 11:53 PM
#8
Re: Will this header only strategy, avoid circular inclusion?
Originally Posted by babaliaris
I will keep in mind creating a 3d middle class that might help solving circular inclusion.
This is kind of the standard way to break up circularly dependent classes. Also many so called design patterns in OO have this purpose for example Mediator. Instead of communicating directly with each other objects communicate indirectly via a mediator without knowing the other objects even exist. It means objects can easily be added and removed without bothering the other objects.
So by designing for loose coupling right from the start one reduces the risk for tight coupling problems later when things change.
The situation where you define them inside the same file, sounds really good!
In fact when you require this in the .hpp files,
Code:
#include "b.h"
#include "a.h"
it effectively is like a.h and b.h were on the same include file.
The advantage in my view of actually having them on the same .h file is that
- the complexity of the include scheme is reduced, and that
- the circularity is contained in one physical place which makes maintenance easier.
You'll notice this if you merge a.h and b.h into a common ab.h file. The circularity is no longer visible on the outside and does not require special care anymore.
Good luck!
Last edited by wolle; September 1st, 2019 at 12:51 AM.
-
September 1st, 2019, 03:09 AM
#9
Re: Will this header only strategy, avoid circular inclusion?
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|