2.7 NamespacesA namespace is a named scope. By grouping related declarations in a namespace, you can avoid name collisions with declarations in other namespaces. For example, suppose you are writing a word processor, and you use packages that others have written, including a screen layout package, an equation typesetting package, and an exact-arithmetic package for computing printed positions to high accuracy with fixed-point numbers. The equation package has a class called fraction, which represents built-up fractions in an equation; the arithmetic package has a class called fraction, for computing with exact rational numbers; and the layout package has a class called fraction for laying out fractional regions of a page. Without namespaces, all three names would collide, and you would not be able to use more than one of the three packages in a single source file. With namespaces, each class can reside in a separate namespace—for example, layout::fraction, eqn::fraction, and math::fraction.
2.7.1 Namespace DefinitionsDefine a namespace with the namespace keyword followed by an optional identifier (the namespace name) and zero or more declarations in curly braces. Namespace declarations can be discontiguous, even in separate source files or headers. The namespace scope is the accumulation of all definitions of the same namespace that the compiler has seen at the time it looks up a given name in the namespace. Namespaces can be nested. Example 2-15 shows a sample namespace definition. Example 2-15. Defining a namespace// The initial declaration namespace numeric { class rational { ... } template<typename charT, typename traits> basic_ostream<charT,traits>& operator<<( basic_ostream<charT,traits>& out, const rational& r); } . . . // This is a second definition. It adds an operator to the namespace. namespace numeric { rational operator+(const rational&, const rational&); } // The definition of operator+ can appear inside or outside the namespace // definition. If it is outside, the name must be qualified with the scope // operator. numeric::rational numeric::operator+(const rational& r1, const rational& r2) { . . . } int main( ) { using numeric::rational; rational a, b; std::cout << a + b << '\n'; } You can define a namespace without a name, in which case the compiler uses a unique, internal name. Thus, each source file's unnamed namespace is separate from the unnamed namespace in every other source file. You can define an unnamed namespace nested within a named namespace (and vice versa). The compiler generates a unique, private name for the unnamed namespace in each unique scope. As with a named namespace, you can use multiple namespace definitions to compose the unnamed namespace, as shown in Example 2-16. Example 2-16. Unnamed namespaces#include <iostream> #include <ostream> namespace { int i = 10; } namespace { int j; // Same unnamed namespace namespace X { int i = 20; // Hides i in outer, unnamed namespace } namespace Y = X; int f( ) { return i; } } namespace X { int i = 30; // X::unnamed is different namespace than ::unnamed. namespace { int i = 40; // Hides ::X::i, but is inaccessible outside the unnamed // namespace int f( ) { return i; } } } int main( ) { int i = X::i; // ambiguous: unnamed::X or ::X? std::cout << ::X::f( ) << '\n'; // Prints 40 std::cout << Y::i << '\n'; // Prints 20 std::cout << f( ) << '\n'; // Prints 10 } The advantage of using an unnamed namespace is that you are guaranteed that all names declared in it can never clash with names in other source files. The disadvantage is that you cannot use the scope operator (::) to qualify identifiers in an unnamed namespace, so you must avoid name collisions within the same source file. Declarations outside of all namespaces, functions, and classes are implicitly declared in the global namespace. A program has a single global namespace, which is shared by all source files that are compiled and linked into the program. Declarations in the global namespace are typically referred to as global declarations. Global names can be accessed directly using the global scope operator (the unary ::), as described earlier in Section 2.3.1. 2.7.2 Namespace AliasesA namespace alias is a synonym for an existing namespace. You can use an alias name to qualify names (with the :: operator) in using declarations and directives, but not in namespace definitions. Example 2-17 shows some alias examples. Example 2-17. Namespace aliasesnamespace original { int f( ); } namespace = original; // Alias int ns::f( ) { return 42; } // OK using ns::f; // OK int g( ) { return f( ); } namespace ns { // Error: cannot use alias here int h( ); } A namespace alias can provide an abbreviation for an otherwise unwieldy namespace name. The long name might incorporate a full organization name, deeply nested namespaces, or version numbers: namespace tempest_software_inc { namespace version_1 { ... } namespace version_2 { ... } } namespace tempest_1 = tempest_software_inc::version_1; namespace tempest_2 = tempest_software_inc::version_2; 2.7.3 using DeclarationsA using declaration imports a name from one namespace and adds it to the namespace that contains the using declaration. The imported name is a synonym for the original name. Only the declared name is added to the target namespace, which means using an enumerated type does not bring with it all the enumerated literals. If you want to use all the literals, each one requires its own using declaration. Because a name that you reference in a using declaration is added to the current namespace, it might hide names in outer scopes. A using declaration can also interfere with local declarations of the same name. Example 2-18 shows some examples of using declarations. Example 2-18. using declarationsnamespace numeric { class fraction { . . . }; fraction operator+(int, const fraction&); fraction operator+(const fraction&, int); fraction operator+(const fraction&, const fraction&); } namespace eqn { class fraction { . . . }; fraction operator+(int, const fraction&); fraction operator+(const fraction&, int); fraction operator+(const fraction&, const fraction&); } int main( ) { numeric::fraction nf; eqn::fraction qf; nf = nf + 1; // OK: calls numeric::operator+ qf = 1 + qf; // OK: calls eqn::operator+ nf = nf + qf; // Error: no operator+ using numeric::fraction; fraction f; // f is numeric::fraction f = nf + 2; // OK f = qf; // Error: type mismatch using eqn::fraction; // Error: like trying to declare // fraction twice in the same scope if (f > 0) { using eqn::fraction; // OK: hides outer fraction fraction f; // OK: hides outer f f = qf; // OK: same types f = nf; // Error: type mismatch } int fraction; // Error: name fraction in use } You can copy names from one namespace to another with a using declaration. Suppose you refactor a program and realize that the numeric::fraction class has all the functionality you need in the equation package. You decide to use numeric::fraction instead of eqn::fraction, but you want to keep the eqn interface the same. So you insert using numeric::fraction; in the eqn namespace. Incorporating a name into a namespace with a using declaration is not quite the same as declaring the name normally. The new name is just a synonym for the original name in its original namespace. When the compiler searches namespaces under argument-dependent name lookup, it searches the original namespace. Example 2-19 shows how the results can be surprising if you are not aware of the using declaration. The eqn namespace declares operator<< to print a fraction, but fraction is declared in the numeric namespace. Although eqn uses numeric::fraction, when the compiler sees the use of operator<<, it looks in only the numeric namespace, and never finds operator<<. Example 2-19. Creating synonym declarations with using declarationsnamespace eqn { using numeric::fraction; // Big, ugly declaration for ostream << fraction template<typename charT, typename traits> basic_ostream<charT,traits>& operator<<( basic_ostream<charT,traits>& out, const fraction& f) { out << f.numerator( ) << '/' << f.denominator( ); return out; } } int main( ) { eqn::fraction qf; numeric::fraction nf; nf + qf; // OK because the types are the same std::cout << qf; // Error: numeric namespace is searched for operator<<, but // not eqn namespace } The using declaration can also be used within a class. You can add names to a derived class from a base class, possibly changing their accessibility. For example, a derived class can promote a protected member to public visibility. Another use of using declarations is for private inheritance, promoting specific members to protected or public visibility. For example, the standard container classes are not designed for public inheritance. Nonetheless, in a few cases, it is possible to derive from them successfully. Example 2-20 shows a crude way to implement a container type to represent a fixed-size array. The array class template derives from std::vector using private inheritance. A series of using declarations make selected members of std::vector public and keep those members that are meaningless for a fixed-size container, such as insert, private. Example 2-20. Importing members with using declarationstemplate<typename T> class array: private std::vector<T> { public: typedef T value_type; using std::vector<T>::size_type; using std::vector<T>::difference_type; using std::vector<T>::iterator; using std::vector<T>::const_iterator; using std::vector<T>::reverse_iterator; using std::vector<T>::const_reverse_iterator; array(std::size_t n, const T& x = T( )) : std::vector<T>(n, x) {} using std::vector<T>::at; using std::vector<T>::back; using std::vector<T>::begin; using std::vector<T>::empty; using std::vector<T>::end; using std::vector<T>::front; using std::vector<T>::operator[]; using std::vector<T>::rbegin; using std::vector<T>::rend; using std::vector<T>::size; }; See Chapter 6 for more information about using declarations in class definitions. 2.7.4 using DirectivesA using directive adds a namespace to the list of scopes that is used when the compiler searches for a name's declaration. Unlike a using declaration, no names are added to the current namespace. Instead, the used namespace is added to the list of namespaces to search right after the innermost namespace that contains both the current and used namespaces. (Usually, the containing namespace is the global namespace.) The using directive is transitive, so if namespace A uses namespace B, and namespace B uses namespace C, a name search in A also searches C. Example 2-21 illustrates the using directive. Example 2-21. The using directive#include <iostream> #include <ostream> namespace A { int x = 10; } namespace B { int y = 20; } namespace C { int z = 30; using namespace B; } namespace D { int z = 40; using namespace B; // Harmless but pointless because D::y hides B::y int y = 50; } int main( ) { int x = 60; using namespace A; // Does not introduce names, so there is no conflict // with x using namespace C; using namespace std; // To save typing std::cout repeatedly cout << x << '\n'; // Prints 60 (local x) cout << y << '\n'; // Prints 20 cout << C::y << '\n'; // Prints 20 cout << D::y << '\n'; // Prints 50 using namespace D; cout << y << '\n'; // Error: y is ambiguous. It can be found in D::y and C's // use of B::y. } 2.7.5 How to Use NamespacesNamespaces have no runtime cost. Don't be afraid to use them, especially in large projects in which many people contribute code and might accidentally devise conflicting names. The following are some additional tips and suggestions for using namespaces:
|