Making Your C++ Namespace Solid and Future-Proof
Minimize namespace pollution and reduce chance of breaking changes (compilation errors) on client code when you ship the next version of your awesome library
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 <functional> 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 <MyClass.h> using namespace PUBLIC_NAMESPACE; int main(){ MyClass a; Foo b; //allowed (public namespace polluted) } With forwarding: #include <MyClass.h> 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
Article updates
14/11/2015 17:10 added usage exampleRelated Tutorials
Balancing Game Development and Creative Direction in Indie Production
A practical look at how indie developers can balance creative direction with hands-on game development. This article co…
My Unreal Engine Development Process: From Core Idea to Playable Build
A practical overview of my Unreal Engine development process, covering how I move from a core game idea to a playable b…
Introducing LaneGraph: The Ultimate Road Network Solution for Unity
Discover the power of LaneGraph, a lightweight and flexible lane-based navigation system for Unity. LaneGraph makes it…
Retargeting Mixamo Characters with Root Motion In Unreal Engine 5.4.
I have always found Retargeting Mixamo Characters To have Root Motion is a serious lengthy Tast, Recently I stumbled up…
How To Make A SIMPLE Main Menu In Unity
In this tutorial for unity, i go over how to make a simple main menu for unity, it's an unlisted video because i do not…
Guide to Gameplay Balance
A perspective on competitive gameplay balance, from a background of "shooter" sandbox design.
Discussion