Reputation: 1278
I have written a simple templated, module-based header library. With module-based, I mean that one can include only string.h
or dynarray.h
and the header will pull in all of its dependencies.
Now I'm facing an issue with missing types because of the way this system works. A module does:
#include
all dependenciesclass Foo
#include
an implementation fileUnfortunately, in some situations, two interfaces need to be available before including any implementations. I have broken down the problem here:
string.h
#pragma once
// A string depends on a DynArray.
#include "dynarray.h"
template<typename E>
class String {
public:
DynArray<E> arr;
/* ... */
};
// Include the implementation of all the different functions (irrelevant here)
#include "string_impl.h"
dynarray.h
#pragma once
// The dynarray header has no direct dependencies
template<typename E>
class DynArray {
public:
/* ... */
E& Get(int index);
};
// Include the implementation of all the different functions
#include "dynarray_impl.h"
dynarray_impl.h
#pragma once
// The dynarray implementation needs the OutOfRangeException class
#include "out_of_range_exception.h"
template<typename E>
E& DynArray<E>::Get(int index) {
if (index >= size) {
throw OutOfRangeException("Some error message");
}
}
out_of_range_exception.h
class OutOfRangeException {
public:
String message;
OutOfRangeException(String message) {
/* ... */
}
};
Due to including the implementation of a module at its bottom, when including string.h
somewhere, the content of dynarray_impl.h
and with it out_of_range_exception.h
comes before the string class interface. So String
is not defined in OutOfRangeException
.
Obviously, the solution is to delay only the implementation part of dynarray (dynarr_impl.h
) after the definition of the string interface. The problem is that I have no idea how to do this without creating some kind of common header file, which is not compatible with a module based approach.
Upvotes: 2
Views: 40
Reputation: 275760
Your problem is that you have one file for both interface and implementation.
#include
ing that file represents both depending on the interface of X and the implementation of X.
Sometimes you just want to depend on the interface of X.
X interface:
#include
all dependencies of the interfaceclass X
.X implementation:
#include
the interface#include
all dependencies of the implementationclass X
.In a sense, these are two separate modules, where one depends on the other. This permits clients to depend only on the interface of another type, or only on its implementation, or first on the interface, then later on the implementation.
Usually you can just #include "X.h"
, except when you have a circular dependency of implementations. Then somewhere you have to break the chain with a #include "X_interface.h"
If you really want to use a single header file, you can do away with #pragma once
and have header files that can be included in "two modes". This can massively slow down build times as any such mechanism will require the compiler to open files just to check if there is any code there; most compilers can detect #ifdef
header-guards and #pragma once
and avoid reopening files it knows won't contain anything of interest. But a fancy "can be included multiple times in different modes" header file cannot be handled by that technique.
Upvotes: 2