This article provides a possible solution to a real problem, nothing more, nothing less. It is up to developers evaluating pros and cons to decide if this approach is worthwhile for their framework/library. The final goal is to mimik the "import" feature of languages like Java or C#. That will be usefull for large codebases or standard codebases, but it is of no use for small code-bases or home-made projects.
The problem
In C++ you do not import stuff, you include files which means "text replacement" and some preprocessor magic. When you include a file you are indirectly including many other files, relying on this behaviour is bad and can cause harm in the long run:- What you expect "by design" is that you have at your disposal only what you "imported/included"
- Side-included headers are actually only a implementation detail: it may change!
Two real examples of breaking code
Example #1 This GCC distribution at least have a STL library that indirectly include
from other headers, when you accidentally use stuff from such a header then the code will just compile fine, but when you try to compile the code from elsewhere the compilers will complain that there is no such thing called "std::function" and (maybe) you are missing some include (and you are truly missing an include).
Example #2
Your class is using another class as a private member:
#include "Foo.h" // a implementation detail
class MyClass{
Foo _foo;
public:
//...
};
Later you decide to refactor the code and use internally another class:
#include "Bar.h" //ops.. any client used "foo" but not included it? => dang compile error for him
class MyClass{
Bar _bar;
public:
//...
};
The Solution
The solution to the problem is actually very simple: Put everything in another namespace, and "import" it only if client is actually including it from the right header.Your library BEFORE
Directory structure:
mylib/
+MyClass.h
+Foo.h
+MyClass.cpp
+Foo.cpp
MyClass.h: including this file actually cause the inclusion of "Foo.h".
#pragma once
#include "Foo.h" // a implementation detail
namespace mylib{
class MyClass{
Foo _foo;
public:
//...
};
}
MyClass.cpp
#include "MyClass.h" // a implementation detail
namespace mylib{
//...
}
Foo.h
#pragma once
namespace mylib{
class Foo{
//...
};
}
Your library AFTER
Directory structure:
mylib/
+MyClass.h
+Foo.h
priv/
+MyClass.h
+Foo.h
+MyClass.cpp
+Foo.cpp
You move all old files to a private folder, then you just import stuff into your namespace from public headers
Forwarding headers
mylib/MyClass.h
#include "priv/MyClass.h"
namespace PUBLIC_NAMESPACE{
using MyClass = PRIVATE_NAMESPACE::MyClass; //requires C++11
}
mylib/Foo.h
#include "priv/Foo.h"
namespace PUBLIC_NAMESPACE{
using Foo = PRIVATE_NAMESPACE::Foo; //requires C++11
}
Internally you keep everything in a private namespace, so the user is forced to include correct headers immediatly:
Now entering the "priv" folder
mylib/ priv/ MyClass.h
#pragma once
#include "Foo.h"
namespace PRIVATE_NAMESPACE{
class MyClass{
Foo _foo;
public:
//...
};
}
Note how important is the usage of "relative path" inclusion
mylib/ priv/ MyClass.cpp
#include "MyClass.h" // a implementation detail
namespace PRIVATE_NAMESPACE{
//...
}
mylib/ priv/ Foo.h
#pragma once
namespace PRIVATE_NAMESPACE{
class Foo{
//...
};
}
Apart from renaming namespaces, there are no major changes in the pre-existing code nor pre-processor magic, the whole task could be automated so that you get C#-style headers almost for free. Basically you can continue to develop as always because it is always possible to re-import stuff in a different namespace (even third party libraries).
Effects on client code:
Without forwarding:
#include
using namespace PUBLIC_NAMESPACE;
int main(){
MyClass a;
Foo b; //allowed (public namespace polluted)
}
With forwarding:
#include
using namespace PUBLIC_NAMESPACE;
int main(){
MyClass a;
Foo b; //NOT ALLOWED Compile error (need to include Foo)
}
Pros
- Less pollution in public namespace
- Users are forced to not rely on implementation details
- Less chance to break code after library refactoring
- Increased compile time
- More maintenance cost for library developers
Thankfully, that'll change in just a few years.