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

    Making Your C++ Namespace Solid and Future-Proof

    General and Gameplay Programming

    DemonDar

    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 [code] [/code] 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: [code] #include "Foo.h" // a implementation detail class MyClass{ Foo _foo; public: //... }; [/code] Later you decide to refactor the code and use internally another class: [code] #include "Bar.h" //ops.. any client used "foo" but not included it? => dang compile error for him class MyClass{ Bar _bar; public: //... }; [/code]

    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: [code] mylib/ +MyClass.h +Foo.h +MyClass.cpp +Foo.cpp [/code] MyClass.h: including this file actually cause the inclusion of "Foo.h". [code] #pragma once #include "Foo.h" // a implementation detail namespace mylib{ class MyClass{ Foo _foo; public: //... }; } [/code] MyClass.cpp [code] #include "MyClass.h" // a implementation detail namespace mylib{ //... } [/code] Foo.h [code] #pragma once namespace mylib{ class Foo{ //... }; } [/code]

    Your library AFTER

    Directory structure: [code] mylib/ +MyClass.h +Foo.h priv/ +MyClass.h +Foo.h +MyClass.cpp +Foo.cpp [/code] 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 [code] #include "priv/MyClass.h" namespace PUBLIC_NAMESPACE{ using MyClass = PRIVATE_NAMESPACE::MyClass; //requires C++11 } [/code] mylib/Foo.h [code] #include "priv/Foo.h" namespace PUBLIC_NAMESPACE{ using Foo = PRIVATE_NAMESPACE::Foo; //requires C++11 } [/code] 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 [code] #pragma once #include "Foo.h" namespace PRIVATE_NAMESPACE{ class MyClass{ Foo _foo; public: //... }; } [/code] Note how important is the usage of "relative path" inclusion mylib/ priv/ MyClass.cpp [code] #include "MyClass.h" // a implementation detail namespace PRIVATE_NAMESPACE{ //... } [/code] mylib/ priv/ Foo.h [code] #pragma once namespace PRIVATE_NAMESPACE{ class Foo{ //... }; } [/code] 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: [code] #include using namespace PUBLIC_NAMESPACE; int main(){ MyClass a; Foo b; //allowed (public namespace polluted) } [/code] With forwarding: [code] #include using namespace PUBLIC_NAMESPACE; int main(){ MyClass a; Foo b; //NOT ALLOWED Compile error (need to include Foo) } [/code] Pros
    • Less pollution in public namespace
    • Users are forced to not rely on implementation details
    • Less chance to break code after library refactoring
    Cons
    • 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


    Henderson

    Report ·

      

    Share this review


    Link to review
    BHXSpecter

    Report ·

      

    Share this review


    Link to review