Quantcast
Channel: GameDev.net
Viewing all articles
Browse latest Browse all 17825

Making Your C++ Namespace Solid and Future-Proof

$
0
0
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 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
Cons
  • Increased compile time
  • More maintenance cost for library developers

Article updates


14/11/2015 17:10 added usage example

Viewing all articles
Browse latest Browse all 17825

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>