Author : McMillan
Page : 1 Next >>
Namespaces were introduced to the C++ Standard in 1995. This tutorial explains what namespaces are and why they were added to the language. You will see how namespaces can avoid name conflicts and how they facilitate configuration management and version control in large-scale projects. Finally, you will learn how namespaces interact with other language features.
The Rationale Behind Namespaces
In order to understand why namespaces were added to the language in the first place, here's an analogy: Imagine that the file system on your computer did not have directories and subdirectories at all. All files would be stored in a flat repository, visible all the time to every user and application. Consequently, extreme difficulties would arise: Filenames would clash (with some systems limiting a filename to eight characters, plus three for the extension, this is even more likely to happen), and simple actions such as listing, copying, or searching files would be much more difficult. In addition, security and authorization restrictions would be severely compromised.
Namespaces in C++ are equivalent to directories. They can be nested easily, they protect your code from name conflicts, they enable you to hide declarations, and they do not incur any runtime or memory overhead. Most of the components of the C++ Standard Library are grouped under namespace std. Namespace std is subdivided into additional namespaces such as std::rel_ops, which contains the definitions of STL's overloaded operators.
A Brief Historical Background
In the early 1990s, when C++ was gaining popularity as a general purpose programming language, many vendors were shipping proprietary implementations of various component classes. Class libraries for string manipulations, mathematical functions, and data containers were integral parts of frameworks such as MFC, STL, OWL, and others. The proliferation of reusable components caused a name-clashing problem. A class named vector, for instance, might appear in a mathematical library and in another container library that were both used at the same time; or a class named string might be found in almost every framework and class library. It was impossible for the compiler to distinguish between different classes that had identical names. Similarly, linkers could not cope with identical names of member functions of classes with indistinguishable names. For example, a member function
vector::operator==(const vector&);
might be defined in two different classes -- the first might be a class of a mathematical library, whereas the other might belong to some container library.
Large-Scale Projects Are More Susceptible to Name Clashes
Name-clashes are not confined to third party software libraries. In large-scale software projects, short and elegant names for classes, functions, and constants can also cause name conflicts because it is likely that the same name might be used more than once to indicate different entities by different developers. In the pre-namespace era, the only workaround was to use various affixes in identifiers' names. This practice, however, is tedious and error prone. Consider the following:
class string // short but dangerous. someone else may have picked //this name already...
{
//...
};
class excelSoftCompany_string // a long name is safer but tedious. //A nightmare if company changes its name...
{
//...
};
Namespaces enable you to use convenient, short, and intelligible names safely. Instead of repeating the unwieldy affixes time after time, you can group your declarations in a namespace and factor out the recurring affix as follows:
//file excelSoftCompany.h
namespace excelSoftCompany { // a namespace definition
class string {/*..*/};
class vector {/*..*/};
}
Namespace members, like class members, can be defined separately from their declarations. For example
#include <iostream>
using namespace std;
namespace A
{
void f(); //declaration
}
void A::f() //definition in a separate file
{
cout<<"in f"<<endl;
}
int main()
{
A::f();
return 0;
}
Properties of Namespaces
Namespaces are more than just name containers. They were designed to allow fast and simple migration of legacy code without inflicting any overhead. Namespaces have several properties that facilitate their usage. The following sections discuss these properties.
Fully Qualified Names
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 of an identifier consists of its namespaces, followed by a scope resolution operator (::), its class name, and, finally, the identifier itself. Because both namespaces and classes can be nested, the resulting name can be rather long -- but it ensures unique identification:
unsigned int maxPossibleLength =
std::string::npos; //a fully qualified name. npos is a member of string; //string belongs to namespace std
int *p = ::new int; //distinguish global new from overloaded new
However, repeating the fully qualified name is tedious and less readable. Instead, you can use a using declaration or a using directive.
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 identifier (type, operator, function, constant, and so on) in the specified namespace, as if the fully qualified name were supplied. For example
#include <vector> //STL vector; defined in namespace std
int main()
{
using std::vector; //using declaration; every occurrence of vector //is looked up in std
vector <int> vi;
return 0;
}
A using directive, on the other hand, renders all the names of a specified namespace accessible in the scope of the directive. It consists of the following sequence: using namespace, followed by a namespace name. For example
#include <vector> // belongs to namespace std
#include <iostream> //iostream classes and operators are also in namespace std
int main()
{
using namespace std; // a using-directive; all <iostream> and <vector> //declarations now accessible
vector <int> vi;
vi.push_back(10);
cout<<vi[0];
return 0;
}
Look back at the string class example (the code is repeated here for convenience):
//file excelSoftCompany.h
namespace excelSoftCompany
{
class string {/*..*/};
class vector {/*..*/};
}
You can now access your own string class as well as the standard string class in the same program as follows:
#include <string> // std::string
#include "excelSoftCompany.h"
int main()
{
using namespace excelSoftCompany;
string s; //referring to class excelSoftCompany::string
std::string standardstr; //now instantiate an ANSI string
return 0;
}
Namespaces Can Be Extended
The C++ standardization committee was well aware of the fact that related declarations can span across several translation units. Therefore, a namespace can be defined in parts. For example
//file proj_const.h
namespace MyProj
{
enum NetProtocols
{
TCP_IP,
HTTP,
UDP
}; // enum
}
//file proj_classes.h
namespace MyProj
{ // extending MyProj namespace
class RealTimeEncoder{ public: NetProtocols detect(); };
class NetworkLink {}; //global
class UserInterface {};
}
In a separate file, the same namespace can be extended with additional declarations.
The complete namespace MyProj can be extracted from both files as follows:
//file app.cpp
#include "proj_const.h"
#include "proj_classes.h"
int main()
{
using namespace MyProj;
RealTimeEncoder encoder;
NetProtocols protocol = encoder.detect();
return 0;
}
Namespace Aliases
As you have observed, choosing a short name for a namespace can eventually lead to a name clash. However, very long namespaces are not easy to use. For this purpose, a namespace alias can be used. The following example defines the alias ESC for the unwieldy Excel_Software_Company namespace. Namespace aliases have other useful purposes, as you will see soon.
//file decl.h
namespace Excel_Software_Company
{
class Date {/*..*/};
class Time {/*..*/};
}
//file calendar.cpp
#include "decl.h"
int main()
{
namespace ESC = Excel_Software_Company; //ESC is an alias for
// Excel_Software_Company
ESC::Date date;
ESC::Time time;
return 0;
}
Koenig Lookup
Andrew Koenig, one of the creators of C++, devised an algorithm for resolving namespace members' lookup. This algorithm, also called argument dependent lookup, is used in all standard-compliant compilers to handle cases such as the following:
CAUTION: Please note that some existing compilers do not yet fully support Koenig lookup. Consequently, the following programs -- which rely on Koenig lookup -- might not compile under compilers that are not fully compliant to the ANSI/ISO standard in this respect.
namespace MINE
{
class C {};
void func;
}
MINE::C c; // global object of type MINE::C
int main()
{
func( c ); // OK, MINE::f called
return 0;
}
Neither a using declaration nor a using directive exists in the program. Still, the compiler did the right thing -- it correctly identified the unqualified name func as the function declared in namespace MINE by applying Koenig lookup.
Koenig lookup instructs the compiler to look not just at the usual places, such as the local scope, but also at the namespace that contains the argument's type. Therefore, in the following source
Page : 1 Next >>