Reputation: 2161
I am a developing a C++ header only library.
There are several parts of the code which follow this pattern:
holder.h
#pragma once
#include "sub.h"
struct Holder
{
void f();
Sub s;
};
sub.h
#pragma once
struct Holder;
struct Sub
{
void g(Holder& h);
};
#include "sub.ipp"
sub.ipp
#include "holder.h"
inline void Sub::g(Holder& h)
{
h.f();
}
sub.h avoids the circular dependency on Holder using forward declaration. However, in holder.h as the Holder class contains a Sub member it needs to see the full declaration of Sub in sub.h. sub.h, however, pulls in the implementation in sub.ipp which can't be instantiated yet as it needs the definition of Holder and we are already inside holder.h so we can't include it again.
As I user of any of these headers I would like to only have to include the correct .h file and not have to worry about manually including the correct .ipp files in strange places.
What is the standard solution to this?
Upvotes: 2
Views: 679
Reputation: 396
I've spent some time figuring this out, here is a technique expanded from @eerorika 's answer.
There are two problematic cases:
If your structs don't fall under any of these cases, you can type out both the declaration and the implementation in the same header file.
For both case 1 and 2, my technique is to split the file into .hpp, .hppdecl and .hppimpl.
Note:
A.hpp:
A.hppdecl: (contains declarations)
A.hppimpl: (contains implementations)
And, do the same for B's files.
Why? Because you must respect the following restrictions/guidelines:
Upvotes: 1
Reputation: 238311
struct Sub { void g(Holder& h); }; void Sub::g(Holder& h) { h.f(); }
Non-inline functions won't work well in header-only libraries, because the headers are typically included into more than one translation unit. You should use inline functions instead.
How can I avoid circular dependencies in a header only library?
You'll have to separate the definition of the functions from the definition of the class. I mean, they are in separate files already, but the header defining the class cannot include the function definitions. That allows breaking the dependency cycle.
This may be a matter of taste, but I also dislike "ipp" headers that don't work standalone.
Example:
detail/holder_class_only.h
#pragma once
#include "detail/sub_class_only.h"
struct Holder
{
inline void f(); // note inline
Sub s;
};
detail/sub_class_only.h
#pragma once
struct Holder;
struct Sub
{
inline void g(Holder& h); // note inline
};
detail/holder_functions.h
#pragma once
#include "detail/holder_class_only.h"
void Holder::f()
{
}
#include "detail/sub_functions.h"
detail/sub_functions.h
#pragma once
#include "detail/sub_class_only.h"
#include "holder.h"
void Sub::g(Holder& h)
{
h.f();
}
sub.h
#pragma once
#include "detail/sub_class_only.h"
#include "detail/sub_functions.h"
holder.h
#pragma once
#include "detail/holder_class_only.h"
#include "detail/holder_functions.h"
Note: Unstested, may contain bugs.
Upvotes: 5