首页 > 代码库 > [Quote] 3.6 Namespaces

[Quote] 3.6 Namespaces

From http://www.informit.com/articles/article.aspx?p=31783&seqNum=6

This chapter is from the book

Navigating C++ and Object-Oriented Design (Bk/CD-ROM)Navigating C++ and Object-Oriented Design (Bk/CD-ROM) 

3.6 Namespaces

An earlier section ("Storage Classes" on page 124) explains scope rules for variables in C++. Program variables may be:

  1. Local (declared inside functions without static and exist in local stack frames)

  2. External static with file scope (declared outside functions with static)

  3. Global with program scope (declared outside functions without static)

  4. Internal static (declared inside functions with static and exist in the data area)

With program scope (3), separately compiled modules require access to function names, structures, and objects, often placed in global namespace. Global namespace includes all names from global declarations; that is, declarations appearing outside of function definitions. What is the proper way to deal with global namespaces? This problem can be significant, especially with applications and libraries. Let‘s investigate how namespaces can help.

Why Use Namespaces?

When programmers use separately compiled modules, name conflicts may occur in the global namespace. Consider the following geometry.h include file, for instance.

 // geometry.h struct Point { double x, y; };    // global structure name double slope(Point, Point);      // global function prototype

The compiler enters the names for the Point structure and slope() function into the global namespace. Suppose a separate geometry.C module contains the slope() implementation and defines origin, a global variable.

 // geometry.C #include "geometry.h" Point origin = { 0, 0 };        // global variable name double slope(Point a1, Point a2)    // function implementation { . . . }

Another module mymath.C includes geometry.h. This file contains functions that call slope(), but it also defines its own variable origin as global.

 // mymath.C #include "geometry.h" Point origin = { 10, 10 };       // link error, name defined twice . . .

There are no compilation errors with these modules. At link time, however, errors occur because the global name origin is defined twice. Note that type Point is not the problem here; rather, the global name (origin) can only be seen once by the linker.

We can easily fix the problem by renaming origin in mymath.C.

 Point myorigin = { 10, 10 };    // global variable name 

However, this solution is not always possible or desirable. Renaming origin could affect more than one function defined in mymath.C. With namespaces, we have more choices. Instead of only two options for names (global and nonglobal), we divide a global namespace into separate "named" scopes. This approach lets us access variables from these namespaces with a special syntax.

The following sections show you how to define namespaces, access members of namespaces, and bring namespace variables into local scope. Namespaces also let you create shorthand names (aliases) for longer namespace qualifiers. Namespaces are particularly helpful with resolving name conflicts when you create class libraries. We examine this use of namespaces in "Namespaces and Classes" on page 254.

Namespace Definitions

The keyword namespace defines a namespace.

 namespace name {  namespace_body }

This format creates a namespace with qualifier name. Within the braces, namespace_body may include variables, function definitions and prototypes, structures, classes (see "Classes" on page 177), enums, typedefs, or other namespace definitions (see "Nested Namespaces" on page 139). Note that a namespace definition does not terminate with a semicolon.

Namespace definitions appear in header files and in separate modules with function definitions. Here‘s an example.

 namespace Red {     // define namespace Red  int j;        // j is a member of namespace Red  void print(int i)   // print() is a member of namespace Red    { cout << "Red::print() = " << i << endl; } } namespace Blue {     // define namespace Blue  int j;        // j is a member of namespace Blue  void print(int);   // print() is a member of namespace Blue } void sub1() { . . . }  // may access Blue and Red namespaces void sub2() { . . . }  // may access Blue and Red namespaces

Namespace Red has two members: integer j and function print(). Namespace Blue has two members with the same names. Normally, these definitions clash in global namespace, but separate namespace qualifiers (Red and Blue) eliminate the problem. Note that a namespace may include function definitions and function prototypes. The function definitions for sub1() and sub2() have access to all of the Red and Blue namespace members, as you will see shortly.

NOTE

Namespace definitions cannot appear inside functions; that is, they must appear outside functions at global scope.

 int sub3() {  namespace Green {     // error, defined inside function    char ch;    char buffer[1024];  }  . . . } 

Namespace Extensions

Namespaces are extensible; that is, you can append subsequent declarations to previously defined namespaces. Namespace extensions may also appear in files separate from the original namespace definition. The following two definitions of namespace Blue are equivalent to the definition of namespace BigBlue.

 namespace Blue {      // original namespace definition  int j;  void print(int); } namespace Blue {      // namespace extension  char ch;  char buffer[20]; } . . . namespace BigBlue {     // equivalent to the above  int j;  void print(int);  char ch;  char buffer[20]; }

When you create a namespace, avoid placing include files inside the namespace definition.

 // myfile.C namespace math { #include "geometry.h"      // not a good idea  Point origin = { 3, 5 };  . . . }

This approach creates link errors with other modules that do not include geometry.h in the same namespace. Instead, define a namespace in the include file itself, and use namespace extensions for modules that need to add to it.

 // geometry.h namespace math {      // define namespace  struct Point { double x, y; };  double slope(Point, Point); } // myfile.C #include "geometry.h" namespace math {       // namespace extension  Point origin = { 3, 5 };  . . . }

Accessing Namespace Members

The scope resolution operator (page 67) provides access to namespace members.

 namespace_name::member_name 

namespace_name is a previously defined namespace qualifier, and member_name must be declared inside namespace_name. Here‘s an example with our Red and Blue namespaces.

 namespace Red {     // define namespace Red  int j;        // j is a member of namespace Red  void print(int i)   // print() is a member of namespace Red    { cout << "Red::print() = " << i << endl; } } namespace Blue {     // define namespace Blue  int j;        // j is a member of namespace Blue  void print(int);   // print() is a member of namespace Blue } void Blue::print(int k) {  cout << "Blue::print() = " << k << endl; }

Outside the Blue namespace definition, the scope resolution operator associates print() with namespace Blue. Without it, the compiler defines print() as a global function. Here are several more examples with variables and function calls.

 void sub() {  Red::j = 0;           // member of namespace Red  Red::print(5);          // member of namespace Red  j = 5;              // illegal, no local j defined  int j = 10;           // legal, j is a local variable  Blue::j = 5;           // member of namespace Blue }

Inside sub(), the scope operator qualifies the first assignment to j and the call to print() from namespace Red. The last assignment uses j from namespace Blue. Without the scope operator and a namespace qualifier, the compiler reports an error in the second assignment. Note that we may declare and initialize a local j inside sub() without any conflict from namespace Red or Blue.

Unnamed Namespaces

A variable or function defined outside function definitions with the specifier static creates a name in file scope; that is, an external static known only within the file where it‘s declared (see "static" on page 125). External statics do not conflict with the same names defined in different translation units at link time. (A translation unit is a source code file with include files after preprocessing.) Unnamed namespaces make external statics obsolete in ANSI C++. Here‘s the format.

 namespace {  namespace_body }

A left brace immediately follows the keyword namespace without an intervening name qualifier. All members defined innamespace_body are in an unnamed namespace that is guaranteed to be unique for each translation unit. At link time, member names in one unnamed namespace do not conflict with equivalent member names from other unnamed namespaces in separate translation units.

The following example shows why unnamed namespaces are useful.

 namespace {                // unnamed namespace  const int Max = 20;  int buffer[Max];  int counter; } void sub1() {  counter = 0;              // counter in unnamed namespace  for (int i = 0; i < Max; i++)     // Max in unnamed namespace    buffer[i] = i*2;          // buffer in unnamed namespace  . . . }

Unnamed namespaces permit access to Maxbuffer, andcounter without the scope resolution operator. An unnamed namespace also solves our linkage problem in mymath.C (page 134).

 // mymath.C #include "geometry.h" namespace {  Point origin = { 10, 10 };          // no conflict with geometry.h } . . .

Unnamed namespace extensions are possible, but only within the same translation unit.

 // file.C namespace {         // unnamed namespace  int j;  void print(int);     // prototype } namespace {         // same unnamed namespace  void print(int i) { cout << i << endl; } } void sub() {  j = 3;  print(j);        // no ambiguity }

Inside file.C, the prototype and definition for print() are members of the same unnamed namespace. The second unnamed namespace extension surrounding the print()definition is important because it tells the compiler thatprint() is not global. Without it, calls to print() are ambiguous because the compiler cannot distinguish between a global function name and a member of an unnamed namespace.

NOTE

With unnamed namespaces, make sure your function implementations are either inside the namespace definition directly or a member of an unnamed namespace extension in the same translation unit.

Here‘s another example that shows you how global variable scope differs from unnamed namespace scope.

 namespace {            // unnamed namespace  const int Max = 20;  char buffer[Max];  int counter; } int counter = 1;          // legal - global variable void sub2() {  counter++;           // illegal - ambiguous (global or unnamed)?  ::counter = 5;         // legal - global  int counter = 0;        // legal - hides global and unnamed member  counter = 10;          // legal - local variable  . . . }

Inside sub2()counter++ is ambiguous because the compiler cannot distinguish between a global name and an unnamed member with the same name. Furthermore, the scope resolution operator without a namespace qualifier (::counter) refers to a global name, not an unnamed member. It‘s also possible to declare a local counter name, but be careful with this technique. Here, the local counter hides both the globalcounter and the unnamed member.

NOTE

Unnamed namespaces restrict global variables to file scope. Use unnamed namespaces in place of external static. Don‘t use :: to refer to unnamed members, and don‘t mix globals with unnamed members if they have the same name.

Nested Namespaces

Namespace definitions are illegal inside functions, but they may appear inside other namespace definitions. This approach nests namespaces for better data hiding. Here‘s an example.

 namespace Outer {              // namespace definition  int i, j;  namespace Inner {            // nested namespace    const int Max = 20;    char ch;    char buffer[Max];    void print();  } }

For proper access to members of a nested namespace, the scope resolutions operator (::) becomes very important.

 void Outer::Inner::print() {  for (int i = 0; i < Max; i++)        // i is local to for loop    cout << buffer[i] << ‘ ‘;  cout << endl; }

Two levels of scope resolution are necessary to define theprint() function of Inner. Note that we can access chbuffer, and Max without the scope operator inside print().

The following sub() function shows more examples of accessing members of Outer and its nested Inner namespace.

 void sub() {  Outer::i = 10;  Outer::Inner::ch = ‘a‘;  for (int i = 0; i < Outer::Inner::Max; i++)    // i is local    Outer::Inner::buffer[i] = Outer::Inner::ch + i;  Outer::Inner::print();  cout << "Max = " << Outer::Inner::Max << endl; }

Using Directives

Accessing namespace members can be cumbersome, especially with long namespace qualifiers and nested namespaces. Fortunately, there are several ways to bypass namespace qualifiers and make access to namespace members more convenient. Using directives provide access to all namespace members without a namespace qualifier and the scope operator. Here is the format.

 using namespace name; 

The qualifier name following the keywords using and namespace must be a previously defined namespace. Using directives may appear at global scope and local scope (inside blocks, functions, and namespace definitions).

Global Using Directives

Let‘s return to our Blue namespace to show you a using directive at global scope.

 namespace Blue {          // define namespace Blue  int j;              // j is a member of namespace Blue  void print(int);         // print() is a member of namespace Blue } using namespace Blue;        // global using directive void sub1() {  j = 0;              // legal - Blue::j  print(j);            // legal - Blue::print()  . . . } void sub2() {  j = 1;             // legal - Blue::j  print(j);            // legal - Blue::print()  . . . }

The global using directive provides access to j and print() from namespace Blue without a qualifier and without the scope operator. Note that j in sub1() and sub2()are not local variables.

NOTE

Be careful with local variables in functions that hide namespace members.

 void sub3() {  j = 2;          // legal - Blue::j  print(j);         // legal - Blue::print()  int j = 10;        // legal - local j hides Blue::j  Blue::j = 5;       // must qualify  . . . } 

A local declaration for j hides the Blue namespace member. Consequently, a qualification is necessary to access j from namespace Blue.

Local Using Directives

Local using directives appear inside blocks, functions, and namespace definitions. Here‘s an example of a function with a local using directive.

 namespace Blue {         // define namespace Blue  int j;             // j is a member of namespace Blue  void print(int);        // print() is a member of namespace Blue } void sub1() {  using namespace Blue;     // local using directive  j = 0;             // legal - Blue::j  print(j);           // legal - Blue::print()  . . . } void sub2() {  j = 1;             // illegal - j not defined  print(j);           // illegal - print() not defined  . . . }

The local using directive provides access to j and print() from namespace Blue only within sub1(). Inside sub2()j and print() generate compilation errors because no local using directive provides access.

NOTE

The scope of a local using directive is valid only inside the block where it appears.

Potential conflicts may occur with global and local using directives when members of different namespaces have the same name or match global names. The compiler flags the ambiguity when you use the name, not at the using directive. Here‘s an example with local using directives.

 namespace Black {             // define namespace Black  int j;  void print(int);  char ch; } namespace White {             // define namespace White  int j;  void print(int);  double vision; } int j;                  // global j void sub3() {  using namespace Black;         // local using directive for Black  using namespace White;         // local using directive for White  j = 0;                 // illegal - Black::j, White::j, or ::j?  print(5);               // illegal - Black or White print()?  ch = ‘a‘;               // legal - Black::ch  vision = 7.65;             // legal - White::vision  int j = 10;              // local j, hides all others  ::j = 5;                // legal - global j  White::j = 5;             // legal - White::j from White  Black::print(j);            // legal - Black::print(), local j }

The local using directives for namespaces Black and White make unqualified access to j and print() ambiguous. Access to variables ch and vision are not ambiguous, however. Note that the local j in sub3() makes qualification necessary to access the global j and the j member in White.

Local using directives are also legal inside namespace definitions. These using directives are transitive, as shown in the following example.

 namespace Black {          // define namespace Black  int j;  void print(int);  char ch; } namespace Gray {          // define namespace Gray  using namespace Black;      // local using directive  int j;              // separate from Black::j  double talk; } namespace Black {          // namespace extension  bool contrast; } void sub3() {  using namespace Gray;      // local using directive  print(5);            // legal - Black::print()  ch = ‘a‘;            // legal - Black::ch  talk = 5.67;           // legal - Gray::talk  j = 22;             // illegal - Black::j or Gray::j?  Black::j = 22;          // legal - Black::j  contrast = true;         // legal - Black::contrast  . . . }

Namespace Gray contains a local using directive for namespace Black, making Black members jprint(), and ch available inside Gray. Note that Gray::j andBlack::j are separate namespace members. Without a qualification inside sub3(), the compiler reports ambiguity errors in the assignment to j.

A namespace extension adds contrast to the Black namespace, but contrast is not accessible inside Gray. All of Black‘s members are accessible in sub3() because the namespace extension for Black appears before the local using directive.

Using Declarations

Using directives make all namespace members accessible without qualification. In contrast, using declarations qualify only individual namespace members. Using declarations are declarations and, as such, create declarations in the current scope. Here is the format.

 using namespace_name::member_name; 

The previously defined namespace qualifier namespace_name follows the keyword usingmember_name must be declared inside namespace_name. Using declarations may appear at global scope and local scope (inside blocks, functions, and namespace definitions).

Here are several examples of using declarations.

 namespace Black {           // define namespace Black  int j;  void print(int);  char ch; } namespace White {           // define namespace White  int j;  void print(int);  double vision; } using White::vision;         // global using declaration void sub1() {  using Black::print;        // local using declaration  ch = ‘a‘;             // illegal - ch not defined  vision = 7.65;           // legal - White::vision  print(5);             // legal - Black::print()  . . . } void sub2() {  print(5);             // illegal - print() not defined  vision = 3.45;          // legal - White::vision  . . . }

A global using declaration for White::vision makes this namespace member accessible to both sub1() and sub2() without qualification. The local using declaration brings Black::print() into local scope for only sub1(). Invoking print(5), therefore, calls Black::print(5) in sub1() but is illegal in sub2().

NOTE

Be careful when mixing using directives and using declarations in the same scope.

 void sub3() {  using namespace White;         // local using directive  using Black::print;          // local using declaration  print(5);               // legal - Black::print()  j = 2;                 // legal - White::j  using Black::j;            // local using declaration  j = 3;                 // legal - Black::j;  int j = 10;              // illegal - j already defined  . . . }

The local using directive inside sub3() makes all of White‘s members accessible. The local using declaration makes print() the only member accessible from Black. The assignment j=2 uses White::j because Black::j is not in local scope at this point. When we bring Black::j into local scope with a using declaration, subsequent assignments modify the j member in Black. The declaration for j is illegal because it is already in local scope.

Using declarations are also convenient with libraries from different sources. Consider the following math_lib and geo_lib namespaces, which both define Point but have different initial values for their origin members.

 namespace math_lib {           // Math Library  struct Point { double x, y; };  Point origin = { 0, 0 };  double slope(Point, Point);  double max(double a, double b);  int max(int a, int b); } namespace geo_lib {           // Geometry Library  struct Point { double x, y; };  struct Line { . . . };  struct Angle { . . . };  Point origin = { 10, 10 }; }

Using declarations specify which namespace members a sub1() function may access.

 math_lib::Point origin = { 5, 5 };      // global declaration void sub1() {  using math_lib::Point;           // Point from math_lib  using geo_lib::Line;            // Line from geo_lib  using geo_lib::origin;           // origin from geo_lib  using math_lib::slope;           // slope() from math_lib  using math_lib::origin ;          // error, multiple declaration  cout << origin.x << endl;         // legal - geo_lib::origin  cout << ::origin.x << endl;        // legal - global origin  . . . }

The global declaration for origin uses type Point from math_lib. Inside sub1(), local using declarations specify which members we want from each namespace. The using declaration for math_lib::origin is illegal because origin is already in local scope from namespace geo_lib. Note that the scope operator is necessary to access the global origin in the last cout statement. Otherwise, we refer to the origin member in geo_lib.

NOTE

Using declarations specify names without type information. The following using declaration, for instance, brings both max() functions from namespace math_lib into the local scope of sub2().

 void sub2() {  using math_lib::max;            // all max functions  cout << max(10, 20) << endl;        // max(int, int)  cout << max(5.6, 7.8) << endl;       // max(double, double) }

When more than one function has the same name, all functions with that name are brought into local scope (see "Overloading and Namespaces" on page 286).

Namespace Aliases

Namespace aliases create synonym names for namespaces. You‘ll want to use aliases when a namespace has a long name. Here‘s the format.

 namespace alias_name = namespace_name; 

Follow the keyword namespace with your alias (alias_name). namespace_name must be a previously defined namespace. You can use more than one alias for the same namespace qualifier, but you can‘t alias an existing alias. Aliases apply only to the translation unit where they appear; the linker sees the original name.

Namespace aliases are convenient shorthand names.

 namespace A_Very_Long_Library_Name {  struct Point { double x, y; };  Point origin = { 10, 10 }; } namespace ALN = A_Very_Long_Library_Name;   // alias void sub() {  using ALN::Point;              // using declaration  Point first = ALN::origin;         // namespace member  . . . }

Namespace aliases may appear in using directives, using declarations, and qualified namespace members.

Namespaces with Program Development

Let‘s put namespaces to work in a complete program. The following geometry.h header file defines a namespace for a geometry library.

Listing 3.14 geometry.h definitions

 #ifndef GEOMETRYH #define GEOMETRYH // geometry.h - geometry definitions namespace Geometry {       // namespace definition  struct Point {    double x, y;  };  double slope(Point, Point); } #endif

Namespace Geometry includes a Point type and a slope() function. Additional definitions appear in geometry.C.

Listing 3.15 geometry.C implementations

 // geometry.C - geometry implementations #include "geometry.h" namespace Geometry {       // namespace extension  Point origin = { 0, 0 }; } double Geometry::slope(Point a1, Point a2) {  double dy = a2.y - a1.y;  double dx = a2.x - a1.x;  if (dx == 0)    throw "slope(): undefined slope";  return dy / dx; }

A namespace extension adds an origin member to Geometry. The slope() function calculates slopes from Point values and throws character string exceptions if the slope denominator is zero.

Here‘s an application that uses this Geometry namespace to calculate slopes for Point variables.

Listing 3.16 mymath3.C — geometry application

 // mymath3.C - namespaces with program development #include <iostream.h> #include "geometry.h" namespace Geo = Geometry;      // alias using Geo::Point;          // using declaration using Geo::slope;          // using declaration namespace {             // unnamed namespace  Point origin = { 10, 10 }; } int main() {  try {    Point a = { 3, 5 };    Point b = { 6, 10 };    cout << "Line a_b has slope " << slope(a, b) << endl;    cout << "Line origin_a has slope " << slope(origin, a) << endl;  }  catch (char *msg) {        // catch handler    cout << msg << endl;    return 1;  }  return 0; } $ CC mymath3.C geometry.C -o mymath3 $ mymath3 Line a_b has slope 1.66667 Line origin_a has slope 0.714286

The program creates a Geo alias for the Geometry namespace qualifier. Global using declarations use alias Geo to bring Point and slope() from the Geometry namespace to global scope. An unnamed namespace defines a global origin that does not conflict with the origin member in Geometry. Inside the try block, we define Point variables a and b before calculating their slopes. The catch handler displays error messages from character string exceptions thrown by slope().

[Quote] 3.6 Namespaces