What's in a namespace? Dan explains.
Copyright © 1997 by Dan Saks
Since it formed, the C++ standards committee has added many features to C++, including templates, exception handling, run-time type information, new-style casts, and namespaces. The committee has also completely reinvented the Standard Library.
Almost of all the new language features were pure extensions rather than changes to existing features. For the most part, if you don't want to bother with the new features, you can ignore them and program in C++ as it was before the standards committee let loose. (EC++, the Embedded C++ subset, is essentially C++ without all the extensions.)
On the other hand, it's harder to ignore the changes to the library. Beyond inventing many new components, the committee changed much of what was already there. Consequently, you'll probably have to rewrite even the simplest C++ program that uses any library facilities if you want to keep it in line with the draft C++ Standard.
As in C, C++ programs access library facilities by including headers. All of the headers in the C library have names that end in .h. For compatibility with C, the C headers appear with the same names in the C++ library. This much has not changed over the years.
The ARM [1] hardly mentioned any library headers, but when it did, it also used header names with .h suffixes, such as <iostream.h> and <new.h>. Not all compiler vendors took the hint. Some C++ compilers had headers with names ending in .hpp or .hxx.
In choosing a convention for headers names, the C++ standards committee opted for none of the above. Headers in the C++ library, other than those from the C library, now have names with no suffix at all. For example, the header that was once <iostream.h> is now <iostream>. The committee also added a header whose name has no .h suffix for each header from the C library. For example, there is now a header <cstring> that effectively replaces <string.h>. The headers whose names end in .h have been deprecated, meaning they may disappear from a future revision of the C++ standard.
In most C++ implementations, each header name maps into a file containing declarations. However, the spelling of the file name, and whether such a file even exists, depends on the implementation. For instance, if <iostream> does map into a file, the file name could be iostream, iostream.h, iostream.hpp, or some other variation on the name iostream. If the header doesn't map into a file, the compiler must provide some other way to transform the header into the appropriate declarations.
Listing 1 shows a version of "Hello, world" written for a typical C++ implementation from earlier in this decade. Listing 2 shows the same program in draft Standard C++. Aside from the difference in the header names (on line 1), the only other different is in the name of the output stream object (on line 5).
In days of old, C++ programmers referred to the standard output stream as simply cout. However, the C++ library now takes advantage of the language's new namespace facilities and places cout, along with nearly every other library component, inside a namespace called std. Hence, the full name for cout is now std::cout.
For reasons that escape me, the C++ Standards committee decided against providing headers to support existing code. For example, the C++ Standard library does not provide a version of <iostream.h> that declares names globally rather than in a namespace. Nothing prevents a compiler vendor from providing .h headers for backward compatibility, and some do. However, such headers are non-standard. Thus, the C++ Standard compels you to confront namespaces, at least a little.
Well, if you have to use namespaces, you might as well understand them. This month, I'll try to explain them, after the following short disclaimer.
I believe I understand the language rules for namespaces. As part of my work on the Plum Hall C++ test suite, I have written dozens of test cases using namespaces. However, test cases aren't "real" programs; they are just perverse little pieces of code designed to stress compilers. I still don't feel like I have a really strong sense of how to use namespaces in "real" programs. I'm not sure anyone else does yet, either.
This month I will just show you what you can do with namespaces. I will try to withhold judgment about whether a particular use is good or poor style. In the coming months, I will present programming examples using namespaces, and use these examples as the context for making judgments.
Global Name Conflicts
The primary motivation for using namespaces is to avoid global name conflicts that can occur when two or more parts of a program use the same global name for different purposes. This happens when one source file includes two different headers, each with its own declaration for the same name.
For example, you might have one header that defines the interface to a pen plotter as:
// plotter.h enum palette { BLACK, RED, GREEN, BLUE }; class plotter { ... private: palette pen_color; ... };and another header that defines the interface to a video driver as:
// video.h enum palette { RED = 1, GREEN = 2, BLUE = 4 }; typedef unsigned char pixel;Any source file that includes both of these headers will encounter compile errors for conflicting definitions for palette, RED, GREEN, and BLUE:
#include "plotter.h" // error: 'palette' and others declared twice #include "video.h"You can avoid these conflicts by declaring the plotter's palette type as a member of the plotter class, as in:
// plotter.h class plotter { public: enum palette { BLACK, RED, GREEN, BLUE }; ... private: palette pen_color; ... };Within the scope of class plotter, you can still refer to palette as palette, BLACK as BLACK, and so on. However, outside the class, you must use fully-qualified names when referring to the member types and constants, as in:
plotter::palette pc; ... pc = plotter::BLACK; ... if (pc == plotter::RED)Using nested types and constants eliminates these specific name conflicts. However, as a general technique for avoiding name conflicts, stuffing names into a class has a few shortcomings.
Moving the palette type into the plotter class actually clarifies the relationship between those types. However, nesting one type inside another to avoid name conflicts is not always so simple. Some headers define several classes that all share a given constant, in which case it's not clear which class should become owner of that constant. Other headers, such as video.h, don't define any classes at all.
For video.h, you could create a class video and dump everything from the header into that class:
// video.h class video { public: enum palette { RED = 1, GREEN = 2, BLUE = 4 }; typedef unsigned char pixel; };Unfortunately, other programmers may misunderstand the purpose of this class because video is more than a name qualifier it's now a type. The program can now declare video objects, or pointers or references to video objects, even though such declarations are nonsense. Declaring a private copy constructor for the class cuts down on such mishaps, but can't make them all go away.
Another problem with stuffing names into classes is that applications that use only one of the classes must still refer to the member names by their fully-qualified names. Using fully-qualified names gets tedious after a while, and may leave the code looking pretty cluttered. Remedies exist, but they are not convenient.
For example, if your application uses only plotter and not video, you can avoid using the fully-qualified names by writing definitions such as:
typedef plotter::palette palette; palette const BLACK = plotter::BLACK, RED = plotter::RED, GREEN = plotter::GREEN, BLUE = plotter::BLUE;However, this is practical only when dealing with a small number of names.
Namespaces
Namespaces provide a way to avoid name collisions without most of the inconveniences of nested classes. A namespace definition bears some similarity to a class definition. It begins with the keyword namespace, followed by an identifier naming the namespace, followed by a sequence of member declarations enclosed in braces. For example:
// video.h namespace video { enum palette { RED = 1, GREEN = 2, BLUE = 4 }; typedef unsigned char pixel; }places the names palette, RED, GREEN, and BLUE inside a namespace called video. Names declared within a namespace are said to be members of that namespace.
A namespace cannot have access specifiers (such as public: or private:) interspersed with its member declarations. All members of a namespace are public. A namespace cannot have a trailing semicolon either. For example:
// video.h namespace video { public: // error: access specifier not allowed ... }; // error: semicolon not allowed.A namespace defines a new scope. The members of a namespace have namespace scope. Code appearing outside the namespace must refer to namespace members by their fully-qualified names. For example,
#include "video.h" ... video::pixel VGA_page[640, 480]; ... VGA_page[r, c] = video::BLUE;Unlike a class definition, which must be contiguous, a namespace definition can be split into several parts spread over one or more translation units. For example,
namespace N { typedef ... T; void f(T &); }introduces namespace N with members T and f. Sometime later, the definition
namespace N { T g(int); }adds member g to namespace N. Namespace definitions are said to be open because you can always add more members to a namespace.
The C++ library takes advantage of the openness of namespaces to distribute the library components across several headers. As I mentioned earlier, the C++ library declares almost all the library components as members of namespace std. No one header declares all the members. Rather, each header declares a set of related members. For example, the standard header <string> contains:
// <string> namespace std { template <...> class basic_string; ... typedef basic_string<char> string; typedef basic_string<wchar_t> wstring; }and the header <typeinfo> contains:
namespace std { class type_info; class bad_cast; class bad_typeid; }The definition of a namespace member can occur inside its namespace definition. For example,
namespace N { typedef ... T; T &f(T &) { ... } }defines function f inside the definition of namespace N. Alternatively, the function definition can appear outside the namespace, as in:
namespace N { typedef ... T; T &f(T &); } N::T &N::f(T &) { ... }When it appears outside the scope of namespace N, the definition for function f must use the fully-qualified name N::f. C++ considers names that appear after the function name N::f to be in the scope of namespace N, so the T in the parameter list refers to N::T. However, names that appear before the function name are not in the scope of N, so the return type must refer to member T of namespace N by its fully-qualified name N::T.
Before C++ had namespaces, the draft C++ Standard said that names declared outside any class or function have file scope. C++ now treats file scope as just another namespace, so file scope is now called global namespace scope.
Nesting and Aliasing
A namespace definition is itself a declaration, so you can define one namespace inside another. For example,
namespace M { namespace N { void f(); ... } ... }defines namespace N inside namespace M. Outside the scope of M, you must refer to function f from N as M::N::f. Within M, you can refer to it as just N::f.
I believe namespaces were designed to encourage library vendors to wrap their libraries inside namespaces. A vendor who sells more than one distinct library can place each library in its own namespace, and each namespace within the vendor's entire namespace.
For example, suppose Saks & Associates supplied a library of user interface components, and another library supporting a database engine. Saks might provide one header for the user interface:
// saksui.h namespace Saks { namespace user_interface { class view; ... } }and another for the database engine:
// saksdbe.h namespace Saks { namespace database_engine { class datum; ... } }Using the fully-qualified names for library components as in
Saks::user_interface::view *pv;is rather cumbersome, to say the least. To spare you this, C++ lets you define a namespace alias for referring to a namespace. For example,
namespace ui = Saks::user_interface;defines ui as an alias for Saks::user_interface. Thereafter, you can use ui more-or-less as you would any other namespace name, as in:
ui::view *pv;Using-Declarations and Using-Directives
Even using short namespace aliases throughout your code can get tedious, especially when a particular compilation unit uses names from only one namespace. Using-declarations and using-directives make names from namespaces accessible without any qualification.
A using-declaration of the form
using N::m;declares m in the current scope as a synonym for the previously declared member m of namespace N. Thereafter, any reference to m in the current scope is a reference to N::m. A using-directive of the form
using namespace N;specifies that the current scope can refer to any name from namespace N without qualifying the name with N::.
For example, given
namespace N { int i = 0; enum palette { RED, GREEN, BLUE }; void f(char); void f(int); }then the using declaration in
void g(int k) { using N::i; i += k; }lets g use i to refer to N::i.
A using-declaration that names an overloaded function introduces all the overloaded functions into the current scope. For example,
using N::f; f('a'); // call N::f(char) f(1); // call N::f(int)On the other hand, a using-declaration that names an enumerated type introduces only the type name, but not any enumerator names. For example,
using N::palette; palette color; ... color = BLUE; // error ... color = N::BLUE; // okayA using-directive introduces all the names from the namespace, so you can write
using namespace N; palette color; ... color = BLUE; // okayThus, using-declarations and using-directives offer yet more ways to write the "Hello, world" program. Listing 3 shows the program with a using-declaration that introduces just std::cout. Listing 4 shows the program with a using-directive that introduces the entire std namespace. The programs differ only on line 2.
By the way, Microsoft Visual C++ 5.0 can compile all of the Listings. Borland C++ 5.01 can compile Listing 1 only. Apparently, Borland's <iostream> and <iostream.h> are identical and do not declare anything in namespace std.
That's Not All, Folks
The basic functionality of namespaces, using-declarations and using-directives is fairly simple, but very little of C++ is as simple as it first seems. C++ has many more rules regarding namespaces, using-declarations and using-directives which I will try to cover in the coming months.
To me, the namespace rules aren't all that interesting unless you can apply namespace to solve programming problems. I'll try to put namespaces to the test in future programming examples. o
[1] Ellis and Stroustrup. The Annotated C++ Reference Manual (Addison-Wesley, 1990).
Dan Saks is the president of Saks & Associates, which offers training and consulting in C++ and C. He is active in C++ standards, having served nearly seven years as secretary of the ANSI and ISO C++ standards committees. Dan is coauthor of C++ Programming Guidelines, and codeveloper of the Plum Hall Validation Suite for C++ (both with Thomas Plum). You can reach him at 393 Leander Dr., Springfield, OH 45504-4906 USA, by phone at +1-937-324-3601, or electronically at dsaks@wittenberg.edu.