Tuesday, January 18, 2011

Namespaces

Namespaces don’t really fit into the functions category, but they are an important concept that we need to cover before we get to classes and object oriented programming, and this is really as good a place as any.
Pretend you are the teacher of a classroom of students. For sake of example, let’s say there are two boys named “Alex”. If you were to say, “Alex got an A on his test”, which Alex are you referring to? Nobody knows, unless you have a way to disambiguate which Alex you mean. Perhaps you point at one, or use their last names. If the two Alex’s were in different classrooms, there wouldn’t be a problem — the problem is really that there are two things with the same name in the same place. And in fact, as the number of students in the classroom increase, the odds of having two students with the same first name increases exponentially.
A similar issue can arise in programming when two identifiers (variable and/or function names) with the same name are introduced into the same scope. When this happens, a naming collision will result, and the compiler will produce an error because it does not have enough information to resolve the ambiguity. As programs get larger and larger, the number of identifiers increases linearly, which in turn causes the probability of naming collisions to increase exponentially.
Let’s take a look at an example of a naming collision. In the following example, foo.h and goo.h are the header files that contain functions that do different things but have the same name and parameters.
foo.h:
1// This DoSomething() adds it's parameters
2int DoSomething(int nX, int nY)
3{
4    return nX + nY;
5}
goo.h:
1// This DoSomething() subtracts it's parameters
2int DoSomething(int nX, int nY)
3{
4    return nX - nY;
5}
main.cpp:
01#include <foo.h>
02#include <goo.h>
03#include <iostream>
04 
05int main()
06{
07    using namespace std;
08    cout << DoSomething(4, 3); // which DoSomething will we get?
09    return 0;
10}
If foo.h and goo.h are compiled separately, they will each compile without incident. However, by including them in the same program, we have now introduced two different functions with the same name and parameters into the program, which causes a naming collision. As a result, the compiler will issue an error:
c:\\VCProjects\\goo.h(4) : error C2084: function 'int __cdecl DoSomething(int,int)' already has a body
In order to help address this type of problem, the concept of namespaces was introduced.
What is a namespace?
A namespace defines an area of code in which all identifiers are guaranteed to be unique. By default, all variables and functions are defined in the global namespace. For example, take a look at the following snippet:
1int nX = 5;
2int foo(int nX)
3{
4    return -nX;
5}
Both nX and foo() are defined in the global namespace.
In the example program above that had the naming collision, when main() #included both foo.h and goo.h, the compiler tried to put both versions of DoSomething() into the global namespace, which is why the naming collision resulted.
In order to help avoid issues where two independent pieces of code have naming collisions with each other when used together, C++ allows us to declare our own namespaces via the namespace keyword. Anything declared inside a user-defined namespace belongs to that namespace, not the global namespace.
Here is an example of the headers in the first example rewritten using namespaces:
foo.h:
1namespace Foo
2{
3    // This DoSomething() belongs to namespace Foo
4    int DoSomething(int nX, int nY)
5    {
6        return nX + nY;
7    }
8}
goo.h:
1namespace Goo
2{
3    // This DoSomething() belongs to namespace Goo
4    int DoSomething(int nX, int nY)
5    {
6        return nX - nY;
7    }
8}
Now the DoSomething() inside of foo.h is inside the Foo namespace, and the DoSomething() inside of goo.h is inside the Goo namespace. Let’s see what happens when we recompile main.cpp:
1int main()
2{
3    using namespace std;
4    cout << DoSomething(4, 3) << endl; // which DoSomething will we get?
5    return 0;
6}
The answer is that we now get another error!
C:\\VCProjects\\Test.cpp(15) : error C2065: 'DoSomething' : undeclared identifier
What happened is that when we tried to call the DoSomething() function, the compiler looked in the global namespace to see if it could find a definition of DoSomething(). However, because neither of our DoSomething() functions live in the global namespace any more, it failed to find a definition at all!
There are two different ways to tell the compiler which version of DoSomething to use.
The scope resolution operator (::)
The first way to tell the compiler to look in a particular namespace for an identifier is to use the scope resolution operator (::). This operator allows you to prefix an identifier name with the namespace you wish to use.
Here is an example of using the scope resolution operator to tell the compiler that we explicitly want to use the version of DoSomething that lives in the Foo namespace:
1int main(void)
2{
3    using namespace std;
4    cout << Foo::DoSomething(4, 3) << endl;
5    return 0;
6}
This produces the result:
7
If we wanted to use the version of DoSomething() that lives in Goo instead:
?
1int main(void)
2{
3    using namespace std;
4    cout << Goo::DoSomething(4, 3) << endl;
5    return 0;
6}
This produces the result:
1
The scope resolution operator is very nice because it allows us to specifically pick which namespace we want to look in. It even allows us to do the following:
1int main(void)
2{
3    using namespace std;
4    cout << Foo::DoSomething(4, 3) << endl;
5    cout << Goo::DoSomething(4, 3) << endl;
6    return 0;
7}
This produces the result:
7
1
It is also possible to use the scope resolution operator without any namespace (eg. ::DoSomething). In that case, it refers to the global namespace.
The using keyword
The second way to tell the compiler to look in a particular namespace for an identifier is to use the using keyword. The using keyword tells the compiler that if it can not find the definition for an identifier, it should look in a particular namespace to see if it exists there. For example:
1int main(void)
2{
3    using namespace std;
4    using namespace Foo; // look in namespace Foo
5    cout << DoSomething(4, 3) << endl;
6    return 0;
7}
The using namespace Foo line causes DoSomething(4, 3) to resolve to Foo::DoSomething(4, 3). Consequently, this program prints:
7
Similarly, the following example:
1int main(void)
2{
3    using namespace std;
4    using namespace Goo; // look in namespace Goo
5    cout << DoSomething(4, 3) << endl;
6    return 0;
7}
causes DoSomething(4, 3) to resolve to Goo::DoSomething(4, 3). Consequently, this program prints:
1
One more example:
1int main(void)
2{
3    using namespace std;
4    using namespace Foo; // look in namespace Foo
5    using namespace Goo; // look in namespace Goo
6    cout << DoSomething(4, 3) << endl;
7    return 0;
8}
As you might have guessed, this causes an error:
C:\\VCProjects\\Test.cpp(15) : error C2668: 'DoSomething' : ambiguous call to overloaded function
In this case, it couldn’t find DoSomething() in the global namespace, so it looked in both the Foo namespace and the Goo namespace (and the std namespace). Since DoSomething() was found in more than one namespace, the compiler couldn’t figure out which one to use.
The using keyword is scoped just like a normal variable — if it is declared inside a function, it is only in effect during that function. If it is declared outside of a function, it affects the whole file from that point forward. The using keyword can save a lot of typing when you have code that needs to use many identifiers from a particular namespace… like when you’re doing input and output!
Consider the following example:
01int main()
02{
03    using namespace std;
04    cout << "Enter a number: ";
05    int nX;
06    cin >> nX;
07    cout << "You entered " << nX << endl;
08 
09    return 0;
10}
Because of the using namespace std line, this function is identical to the following:
1int main()
2{
3    std::cout << "Enter a number: ";
4    int nX;
5    std::cin >> nX;
6    std::cout << "You entered " << nX << std::endl;
7 
8    return 0;
9}
However, the top example is generally considered easier to read!
Using the using keyword judiciously can make your code neater and easier to read. Although the using keyword can be used outside of a function to help resolve every identifier in the file, this is not recommended, as it increase the chance of identifiers from multiple namespaces conflicting, which somewhat defeats the point of namespaces in the first place.

No comments: