• Advertisement
  • 11/17/15 04:16 AM
    Sign in to follow this  

    Making Your C++ Namespace Solid and Future-Proof

    General and Gameplay Programming

    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

    Article updates

    14/11/2015 17:10 added usage example

      Report Article
    Sign in to follow this  

    User Feedback

    Create an account or sign in to leave a review

    You need to be a member in order to leave a review

    Create an account

    Sign up for a new account in our community. It's easy!

    Register a new account

    Sign in

    Already have an account? Sign in here.

    Sign In Now


    Share this review

    Link to review


    Share this review

    Link to review


    Share this review

    Link to review


    Share this review

    Link to review


    Share this review

    Link to review

  • Advertisement