Making Your C++ Namespace Solid and Future-Proof

Published November 17, 2015 by Dario Oliveri, posted by DemonDar
Do you see issues with this article? Let us know.
Advertisement

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

Article updates

14/11/2015 17:10 added usage example

Cancel Save
0 Likes 11 Comments

Comments

Servant of the Lord

In C++ you do not import stuff, you include files

Thankfully, that'll change in just a few years.

November 17, 2015 05:25 AM
DemonDar

Thankfully, that'll change in just a few years.

You mean C++17 modules right? :) can't wait for that.

November 17, 2015 08:33 AM
apatriarca
I personally find your two cons to be actually bigger problems than the pros. I never had much problems with public namespace pollution. I had problems with some code not compiling because I changed what was included in an header file, but if some code break because it relied on some implementation details then that code is buggy. I don't think it is a library fault. It is also a very easy problem to solve.

Compile time is instead a very big problem in C++ for me. You should really try to reduce it or it will skyrocket when the project grow bigger. You also spend a lot more time maintaining the code than writing it.
November 17, 2015 10:52 AM
LoneDwarf

The problem is that you should never have a using declaration in a header. These should only be in cpp files. Use fully qualified namespace names in your headers. Looks bad at first but eventually it will be normal.

November 17, 2015 02:17 PM
swiftcoder
I'd also argue that including non-template classes in header files is often a warning sign of problems ahead. Wherever possible, forward declare classes and include them only from the .cpp file. Use the PIMPL idiom where necessary to accomplish this.

Your compile times will decrease dramatically, and you'll be leaking far less of your class internals into their public API.
November 17, 2015 05:12 PM
Servant of the Lord

You mean C++17 modules right? smile.png can't wait for that.

Yep, except it'll be in a technical report rather than in C++17.

We'll have access to it in 2017, but it'll be alongside C++17, not inside of it.

It'll (likely) be officially included within C++21, and unofficially included alongside C++17, so it can be tweaked and refined while we as a community test it out, so any design problems can be fixed.

November 17, 2015 05:35 PM
DemonDar

The problem is that you should never have a using declaration in a header. These should only be in cpp files. Use fully qualified namespace names in your headers. Looks bad at first but eventually it will be normal.

Ouch, that's not the same at all. Something like "using namespace std" is really a mess and is potentially able to pollute namespace and causing very much problems, in my case I'm just exporting 1 name for header (or if it makes sense, just a bunch of names) wich is very different from importing a whole namespace.


namespace myPublic{
    using K = myPrivate::K;  // results in myPublic::K name => same as declaring K inside "myPublic"
}
November 19, 2015 07:56 AM
NightCreature83

The problem is that you should never have a using declaration in a header. These should only be in cpp files. Use fully qualified namespace names in your headers. Looks bad at first but eventually it will be normal.

Even in CPP files they should only be used inside of a scope, you never want to pollute your global scope.

November 19, 2015 12:41 PM
LoneDwarf

The problem is that you should never have a using declaration in a header. These should only be in cpp files. Use fully qualified namespace names in your headers. Looks bad at first but eventually it will be normal.

Ouch, that's not the same at all. Something like "using namespace std" is really a mess and is potentially able to pollute namespace and causing very much problems, in my case I'm just exporting 1 name for header (or if it makes sense, just a bunch of names) wich is very different from importing a whole namespace.


namespace myPublic{
    using K = myPrivate::K;  // results in myPublic::K name => same as declaring K inside "myPublic"
}

Yes you're correct. I noticed this after I posted and thought it might make it under the radar :) I used to do this sort of thing to make short hands for stuff like the following but I stopped this practice.


using dmath = dwarf::math;

Still there is some truth here and it's really nitpicking but I still wouldn't do it. Headers get included in different orders all the time and having code that compiles two different ways based on include order can be hard to find. Namespaces are designed to prevent this sort of thing and making shorthand versions is really just circumventing the system.

Sure the compiler should point out ambiguity for you if there is a problem but it doesn't protect you from old C headers with macros. I would also say the code is harder to read now. Don't spend time on stuff like this as it only takes away from finishing a project.

November 19, 2015 03:54 PM
DemonDar

Yes you're correct. I noticed this after I posted and thought it might make it under the radar smile.png I used to do this sort of thing to make short hands for stuff like the following but I stopped this practice.


using dmath = dwarf::math;

Still there is some truth here and it's really nitpicking but I still wouldn't do it. Headers get included in different orders all the time and having code that compiles two different ways based on include order can be hard to find. Namespaces are designed to prevent this sort of thing and making shorthand versions is really just circumventing the system.

Sure the compiler should point out ambiguity for you if there is a problem but it doesn't protect you from old C headers with macros. I would also say the code is harder to read now. Don't spend time on stuff like this as it only takes away from finishing a project.

Maybe I exposed that bad, it is not a shorthand at all. Differently from the "typical use" of creating short-hands, I'm here proposing a good(at least according to my opinion:P) use of "using directive".

There are no problems of include order etc. and there is no stuff like 2 different versions. Semantically is exactly equivalent to declaring a class with no side-included headers (go back and read sentence again). I know it is a subtle difference and hard to notice at a first and at a second read.

November 19, 2015 11:16 PM
LoneDwarf

Yes you're correct. I noticed this after I posted and thought it might make it under the radar smile.png I used to do this sort of thing to make short hands for stuff like the following but I stopped this practice.


using dmath = dwarf::math;

Still there is some truth here and it's really nitpicking but I still wouldn't do it. Headers get included in different orders all the time and having code that compiles two different ways based on include order can be hard to find. Namespaces are designed to prevent this sort of thing and making shorthand versions is really just circumventing the system.

Sure the compiler should point out ambiguity for you if there is a problem but it doesn't protect you from old C headers with macros. I would also say the code is harder to read now. Don't spend time on stuff like this as it only takes away from finishing a project.

Maybe I exposed that bad, it is not a shorthand at all. Differently from the "typical use" of creating short-hands, I'm here proposing a good(at least according to my opinion:P) use of "using directive".

There are no problems of include order etc. and there is no stuff like 2 different versions. Semantically is exactly equivalent to declaring a class with no side-included headers (go back and read sentence again). I know it is a subtle difference and hard to notice at a first and at a second read.

I took a better look at what you are doing (waiting for a build to finish). While my initial comments are true they don't fit the scenario you are talking about, as you pointed out. Sorry. I made a poor assumption that you were trying to solve a different problem. Your solution is actually still looking for a problem and it pains me that someone would spend time on this.

I would strongly suggest reading what the master has to say about it in Large Scale C++ Software Design by Lakos. A dry piece of text but the guy knows exactly what he is talking about and I got a lot out of this book.

The issue is that your header should include everything it needs to compile itself and nothing more. Forward references whenever possible. If it doesn't I would pretty much say it's an error in waiting. Also the cpp that includes it should include it's header first to enforce this principle.

The insanity of having two header files with the same name makes me cringe. Anyone trying to read your code or yourself in 6 months is in for a world of hurt...

December 14, 2015 07:25 PM
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!

Minimize namespace pollution and reduce chance of breaking changes (compilation errors) on client code when you ship the next version of your awesome library

Advertisement
Advertisement

Other Tutorials by DemonDar

Advertisement