Saturday, January 22, 2011

C++ namespace

 
 
using namespace std;

It appears in almost every tutorial for beginners and the vast majority of books dedicated to C++. I suppose it saves more time than the alternative of teaching the proper mechanics to newcomers. For the most part, I suspect that you may never have a problem using the standard namespace in it's entirety. I think it's unfortunate, however, that this approach hides both the intent of namespaces and the pitfalls associated with not understanding their mechanics.

A namespace is a clarification tool: it announces the intended scope of the token it proceeds. If you have two tokens with the same name, placing them in separate namespaces prevents compilation errors due to name clashes. There are other functions of namespaces, such as clarity of code, but these are considered secondary if at all.

I've heard - and argued against - people who claim that using an entire namespace is perfectly fine and any claims to the contrary are bunk. To be clear; understanding namespaces is not as important as understanding pointers, references and proper use of classes. But, there are things to watch out for especially as a beginner. Most notably: masking and ambiguity.

Masking
The problem here is that including an entire namespace can mask errors in your own code. This example is something that is very common in those starting out in C++, particularly if they are coming from a scripted language background.

#include <iostream>
#include <algorithm>

using namespace std;

int main () {
    int x = 5, y = 3;
    cout << "Before swap: " << x << " " << y << endl;
    swap (x, y);
    cout << "After  swap: " << x << " " << y << endl;
    return 0;
}

void swap (int x, int y) {
    int tmp = x;
    x = y;
    y = tmp;
}

The intention is obviously to swap the values of two numbers. As it is written however, this swap will only modify the local variables leaving the original two unchanged. The problem lies in the fact that there is a std::swap defined that does the appropriate thing and we get:

Before swap: 5 3
After  swap: 3 5

Which is correct but masks two things that a beginner now won't notice: 1) the local swap is not recognized by the compiler before the use in main (no prototype) and 2) the std::swap version is being invoked and masking incorrect behavior.

If, instead of the entire namespace, we only used specifically the elements local to our scope we would resolve these issues. Changing 

using namespace std;
to 
using std::cout;
using std::endl;

would result in a compilation error similar to:

mask.cc: In function ‘int main()’:
mask.cc:10: error: ‘swap’ was not declared in this scope

Ambiguity
This is a problem of name clashing: two equally qualified matches to a function are present and the compiler can not discern which is to be used. There is a similar problem when two names clash and scope can be used in determining which function gets used. This is an even more subtle bug since there are no complaints from the compiler your code just uses the function with the innermost scope regardless of your intentions.

Consider:

namespace foo { void fun() { } }
namespace bar { void fun() { } }

using namespace foo;
using namespace bar;

int main () {
    fun();
    return 0;
}

Trying to compile that results in

ambig.cc: In function ‘int main()’:
ambig.cc:8: error: call of overloaded ‘fun()’ is ambiguous
ambig.cc:2: note: candidates are: void bar::fun()
ambig.cc:1: note:                 void foo::fun()

That is a painfully obvious example but working with much larger projects where multiple teams are committing code this becomes a real problem. Again, using only the relevant portions of a namespace or being specific when calling the function (bar::fun(), for example) solves this problem.



Another benefit of understanding how to use namespaces is cleaner code in some settings. Consider some of the code you may have seen for multi-platform projects:

void fun () {
#ifdef PLATFORM1
std::cout << "platform1::fun()" << std::endl;
#else
std::cout << "platform2::fun()" << std::endl;
#endif
}

with the logic of the platform sprinkled within the function bodies themselves. This is hard to read and even harder to maintain. With namespaces this can be cleaned up to separate each function body (one for each platform) into it's own namespace.

#include <iostream>

namespace platform1 {
    void fun () {
        std::cout << "platform1::fun()" << std::endl;
    }
}

namespace platform2 {
    void fun () {
        std::cout << "platform2::fun()" << std::endl;}
}

Now, the only place that needs platform logic is when we decide to use the namespace.

#include "platform.hh"

#ifdef PLATFORM1
namespace platform = platform1;
#else
namespace platform = platform2;
#endif

int main () {
    platform::fun ();
    return 0;
}

I can't remember ever seeing this approach suggested instead of the less robust macro everywhere code.



Again, with all errors you can run across in a C++ program the ones listed above are some of the easier to pinpoint. Beginners will have the most difficulty as they are not only struggling with a new language and syntax but with subtle errors that are masking behavior they don't even realize is incorrect. I don't think that teaching the proper use of namespace adds that much complexity to learning C++; it really is unfortunate that the topic is not done justice in most reference material.

No comments :

Post a Comment