- Overview
-
Table of Contents
- Special Member Functions: Constructors, Destructors, and the Assignment Operator
- Operator Overloading
- Memory Management
- Templates
-
Namespaces
- Applications of Namespaces
- Properties of Namespaces
- Using and Extending Namespaces
- Interaction with Other Language Features
- Online Resources
- Time and Date Library
- Streams
- Object-Oriented Programming and Design Principles
- The Standard Template Library (STL) and Generic Programming
- Exception Handling
- Runtime Type Information (RTTI)
- Signal Processing
- Creating Persistent Objects
- Bit Fields
- New Cast Operators
- Environment Variables
- Variadic Functions
- Pointers to Functions
- Function Objects
- Pointers to Members
- Lock Files
- Design Patterns
- Dynamic Linking
- Tips and Techniques
- Five Things You Need to Know About C++11 Unions
- A Tour of C99
- A Tour of C1X
- C++0X: The New Face of Standard C++
- C++0x Concurrency
- The Reflecting Circle
- We Have Mail
- The Soapbox
- Numeric Types and Arithmetic
- Careers
- Locales and Internationalization
Properties of Namespaces
Last updated Jan 1, 2003.
Namespaces are more than just directories or containers of names. In the following passages, I will discuss some of the namespaces' features and their use.
A Fully Qualified Name
A namespace is a scope in which declarations and definitions are grouped together. In order to refer to any of these from another scope, a fully qualified name is required. A fully qualified name has the form: namespace::classname::identifier. Since both namespaces and classes can be nested, the resulting name can be rather long but it ensures uniqueness:
unsigned int maxlen = std::string::npos;
If repeating the fully qualified name seems tedious, you can use a <I>using declaration</i> or a <I>using directive</i> instead.
A using-Declaration and a using-Directive
A using-declaration consists of the keyword using followed by a namespace::member. It instructs the compiler to locate every occurrence of a certain namespace member (type, operator, function, constant, etc.) in the specified namespace, as if the fully-qualified name were used. For example:
#include <vector> //STL vector; defined in namespace std
int main()
{
// the following is a using declaration; every
//occurrence of vector is looked up in std
using std::vector;
vector <int> vi;
}
A using-directive, on the other hand, renders all the names of a certain namespace accessible in the directive's scope. For example:
#include <vector> // belongs to namespace std
#include <iostream> // also in namespace std
int main()
{
using namespace std; // a using directive
//all <iostream> and <vector> declarations
//can now be accessed without
//fully qualified names
vector <int> vi;
vi.push_back(10);
cout<<vi[0];
}
Let's look back at our string class example:
//file excelSoftCompany.h
namespace excelSoftCompany
{
class string {/*..*/};
class vector {/*..*/};
}
You can access your own string class as well as the standard string class in the same program without risking name clashes like this:
#include <string> // std::string
#include "excelSoftCompany.h"
int main()
{
using namespace excelSoftCompany;
string s; // referring to class excelSoftCompany::string
std::string standardstr; // ANSI string
}
Namespace Aliases
Choosing a short name for a namespace can eventually lead to a name clash -- the very same problem we wanted to avoid in the first place. However, very long namespaces are tiresome. For this purpose, you can use a namespace alias. In the following example, I define an alias called ESC for the unwieldy Excel_Software_Company namespace:
//file decl.h
namespace Excel_Software_Company
{
class Date {/*..*/};
class Time {/*..*/};
}
//file calendar.cpp
#include "decl.h"
int main()
{
namespace ESC = Excel_Software_Company; //alias
ESC::Date date;
ESC::Time time;
}
Argument-Dependent Lookup
Andrew Koenig devised an algorithm known as Koenig lookup for resolving namespace members. This algorithm, also called argument-dependent lookup, is used in all standard-conforming compilers to handle cases like the following:
namespace MYNS
{
class C {};
void func(C);
}
MYNS::C c; // global object of type MYNS::C
int main()
{
func(c); // OK, MYNS::f called
}
Neither a using-declaration nor a using-directive appears in the program. And yet, the compiler did the right thing -- it correctly identified the unqualified name func as the function declared in namespace MYNS by applying Koenig lookup. How does it work? Koenig lookup instructs the compiler to look at not just the usual places such as the local scope, but also the namespace that contains the argument's type. Thus, in the following source line, the compiler detects that the object c, which is the argument of the function func, belongs to namespace MYNS. Consequently, it looks at namespace MYNS to locate the declaration of func, "guessing" the programmer's intent.
func(c); // OK, MYNS::f called
Without Koenig lookup, namespaces would impose an unacceptable burden on the programmer, who would have to repeatedly specify the fully qualified names, or instead, use numerous using-declarations. To push the argument in favor of Koenig lookup even further, consider the following example:
#include<iostream>
using std::cout;
int main()
{
cout<<"hi"; //OK, operator <<
//was brought into scope
//by Koenig lookup
}
The using declaration injects std::cout into the scope of main(), thereby enabling the programmer to use the non-qualified name cout. However, the overloaded << operator, as you may recall, is not a member of std::cout. Rather, it's declared in namespace std as an external function that takes a std::ostream object as its argument. Without Koenig lookup, the programmer would have to write something like this:
std::operator<<(cout, "hi");
Fortunately, Koenig lookup "does the right thing" and saves the programmer from this tedium in an elegant way.
