3.10 NAMESPACES IN C


3.10 NAMESPACES IN C++

The modularity that one gives to Java software through the mechanism of packages can be given to C++ software through the mechanism of namespaces. The basic syntax for encapsulating code in a namespace is

      namespace ModuleName {           // code      } 

All of the code in a given namespace can be in one file, or distributed over multiple files. If more than one file is involved, the code in each file must be encapsulated in the manner shown above.

As the following example illustrates, basically a namespace creates a scope[10] for the identifiers that are its members. The member identifiers declared in a given namespace cannot ordinarily be accessed directly outside that namespace without the scope resolution operator ‘::’. For example, the following namespace, named Module1, encapsulates the names foo, bar and T.

      namespace Module 1 {           void foo() { cout << "Module1 foo() invoked" << endl; }           int bar( int x ) { return x; }           typedef string* T;      } 

To access the function foo() outside the namespace Module1, we must use its namespace-qualified name:

      Module1::foo(); 

Since it can quickly become tedious to always have to use a namespace qualified name in the manner shown above, C++ allows us to employ the using directive, as in

      int main() {           using namespace Module1;           foo();      } 

This would cause foo() of namespace Module1 to be invoked. Obviously, this would only work if there was no name conflict between foo() of Module1 and, say, foo() defined at the global top level, as in

      void foo() { cout << "Top-level foo() invoked" << endl; }      int main() {           using namespace Module1;           foo();                   // ERROR, foo conflict           return 0;      } 

The first foo here, defined with a global scope, could be thought of existing in a nameless global namespace. The names defined in the global namespace in this manner can be accessed by pre-pending the scope operator to them. For example, if we had a need to invoke both the foo of Module1 and the foo of the global scope in the above code fragment, we could rewrite it as

      void foo() { cout << "Top-level foo() invoked" << endl; .}      int main() {           Module1::foo();           // invokes foo of Module1           ::foo();                  // invokes global foo           return 0;      } 

The following program brings together these and other aspects of namespaces. Please read the comments carefully as you scan the code.

 
//Namespaces.cc #include <string> #include <iostream> using namespace std; //(A) namespace Module1 { void foo(); // this is only a declaration, // definition will come later in line (D) } namespace Module2 { void foo() { cout << "Module2 foo() invoked" << endl; } } namespace Module3 { using namespace Module1; // has 'foo' //(B) using namespace Module2; // also has 'foo', but //(C) // no problem at this point void bar() { cout << "Module3 bar() invoked" << endl; } } namespace Module4 { void foo() { cout << "Module4 foo() invoked" << endl; } } namespace Module5 { void bar() { cout << "Module5 bar() invoked" << endl; } } // foo of Module1 defined outside the namespace Module1. Must // therefore use namespace-qualified name for foo: void Module1::foo() {cout << "Module1 foo() invoked" << endl;} //(D) // The global foo: void foo() { cout << "top level foo() invoked" << endl; } //Addition to Module5: namespace Module5 { void hum() { cout << "Module5 hum() invoked" << endl; } } int main() { //This statement invokes global foo() foo(); //(E) Module1::foo(); //(F) Module2::foo(); //(G) //The following statement, if uncommented, results //in compiler error because Module1 and Module2 //both have foo() // Module3::foo(); //(H) Module3::bar(); //(I) using namespace Module4; //The following statement, if uncommented, results //in compiler error because foo() of Module4 //conflicts with the global foo() // foo(); //(J) //But the following statement is okay since it uses //the scope operator for invoking the global foo() ::foo(); //(K) using namespace Module5; bar(); //(L) hum(); //(M) return 0; }

The program produces the output shown below to the left of the the symbol // in each line:

      top level foo() invoked           // from line (E)      Module1 foo() invoked             // from line (F)      Module2 foo() invoked             // from line (G)      Module3 bar() invoked             // from line (I)      top level foo() invoked           // from line (K)      Module5 bar() invoked             // from line (L)      Module5 hum() invoked             // from line (M) 

The using directives in lines (A), (B), and (C) of the above program specify that the names in the indicated namespaces can be used as if they were declared directly in the scope in which the directives occur. The using directive does not actually introduce the namespace names into the current scope, but simply makes them available should they be called upon in the current scope. It is for this reason that any name conflicts between two namespaces pulled into the current scope are not discovered unless those names are actually invoked. That's why the compiler has no problem with the definition of Module3 namespace even though it starts out with the using-directives for Module1 and Module2 in line (B) and (C), both containing the name foo. However, if we tried to invoke

      Module3::foo(); 

in main, as we have shown in the commented out statement in line (H), the name conflict between the foo of Module1 and Module2 would be flagged down by the compiler. So if two or more namespaces injected into the current scope have common names amongst them, the compiler does not care unless one of those common names is invoked in the current scope.

3.10.1 Using Declaration Versus Using Directive

While a using directive makes all its names merely available to the current scope, a using declaration actually declares a specific namespace name in the current scope. The syntax of a using-declaration is a little bit different from that for a using-directive, as illustrated in the following example code. The namespaces Module1 and Module2 both encapsulate the programmer-defined types X and Y. To specifically declare X of Module1 inside main, we say

      using Module1::X; 

in line (A) of the program. Once a specific name is declared in the current scope with a using-declaration, an attempt to declare the same name from another namespace would be flagged down by the compiler. The compiler would declare a name conflict in the following example if we uncomment the commented-out statement in line (B) of main.

 
//Namespaces 2.cc namespace Module1 { class X {}; class Y {}; } namespace Module2 { class X {}; class Y {}; } int main() { using Module 1::X; //(A) X x1; // using Module 2::X; // ERROR, name conflict //(B) X x2; return 0; }

3.10.2 Which Namespace Owns Names Imported from Another Namespace?

If one namespace is imported into another named namespace, the member names of the former can be thought of as belonging to the latter. This is made clear by the following example in which we import the namespace Module1 and a specific name, foo, of the namespace Module2 into the namespace Module3. All of the names invoked in main are with respect to Modules3, even those that are really owned by the other two modules.

 
//Namespaces3.cc #include <iostream> using namespace std; namespace Module1 { class X {}; } namespace Module2 { void foo() { cout << "foo of Module2 invoked" << endl; } void bar() { cout << "bar of Module2 invoked" << endl; } } namespace Module3 { using namespace Module1; typedef X Y; using Module2::foo; class Z {}; } int main() { Module3::X x; Module3::Y y; Module3::foo(); // Module3::bar(); //ERROR. No bar in Module3. return 0; }

3.10.3 Using Declarations and Directives Have Scope

Just like any other declaration, using directives and declarations have block scope.[11] This fact can be used to localize the visibility of the names introduced into a program by either a using directive or a using declaration. This is illustrated in the following program where we have divided the code in main into four separate blocks. As a result, there is no conflict between the name Type imported into the first block from Module1 and the same name Type imported into the second block from Module2. The third and the fourth blocks show a similar localization of the accessibility of names imported through using declarations.

 
//Namespaces.cc #include <iostream> using namespace std; namespace Module1 { typedef int Type; Type foo( Type arg ) { return arg; } } namespace Module2 { typedef double Type; Type foo( Type arg ) { return arg; } } int main() { { using namespace Module1; Type x = 100; // int cout << foo( x ) << endl; // 100 } { using namespace Module2; Type x = 3.14; // double cout << foo( x ) << endl; // 3.14 } { using Module1::foo; cout << foo( 100 ) << endl; // 100 } { using Module2::foo; cout << foo( 3.14) << endl; // 3.14 } return 0; }

3.10.4 Nesting Namespaces and Namespace Aliases

As with classes, it is possible to nest namespaces to any depth. If an inner namespace contains a name that is identical to an outer namespace, the inner name hides the outer name. Also, the namespace-qualified name of an inner namespace starts with the name of the outermost namespace, followed by the scope operator, followed by the name of the next-to-the-outermost namespace, followed by the scope operator, etc., until reaching the inner namespace. These aspects of namespace nesting are illustrated by the following example in which the namespaces N1 through N4 are nested to a depth of four. Each namespace contains a type Type that means different things in the different namespaces.

 
// NamespaceNested.cc #include <iostream> #include <string> using namespace std; namespace N1 { typedef int Type; namespace N2 { typedef int* Type; namespace N3 { typedef string Type; namespace N4 { typedef string* Type; } } } } int main() { using namespace N1; Type x = 10; // Type is int cout << x << endl; // 10 N1::N2::Type p = &x; // Type is int* cout << *p << endl; // 10 N1::N2::N3::Type str( "hello" ); // Type is string cout << str << endl; // "hello" N1::N2::N3::N4::Type q = &str; // Type is string* cout << *q << endl; // "hello" namespace N_FOUR = N1::N2::N3::N4; // namespace alias //(A) N_FOUR::Type ptr = &str; cout << *ptr << endl; // "hello" return 0; }

This example also shows in line (A) how we can define an alias for an unwieldy namespace name. In the example, the namespace qualified name for the innermost namespace is

      N1::N2::N3::N4 

Line (A) of the program defines N_FOUR to be an alias for the innermost namespace name:

      namespace N_FOUR = N1::N2::N3::N4; 

The general syntax for defining an alias for the complete name of a namespace is

      namespace new_name = current_name; 

3.10.5 Unnamed Namespaces

When defining a namespace, you are allowed to omit the name of the namespace:

      namespace {           int buffer;           class X;           void foo()           typedef string* T;      } 

The compiler internally generates a unique name for such a namespace. Furthermore, a using directive is automatically assumed for an unnamed namespace. So, in effect, an unnamed namespace declaration is equivalent to

      namespace —UNIQUE_NAME— {           int buffer;           class X;           void foo();           typedef string* T;      }      using namespace —UNIQUE_NAME—; 

So, for internal bookkeeping purposes, the names declared in an unnamed namespace are transformed by the compiler by mangling the names with the internally generated unique name for the namespace, as is done for a regular namespace but with the programmer-supplied namespace name. Additionally, these names possess internal linkage—the same linkage as for a global name that is declared to be static. As you'll recall, when you declare a global name in file A to be static, its scope is limited to file A. It cannot be linked to from a file B by the use of extern declaration in file B. That allows you to use the same name in file B. for a different purpose without fear of creating a name clash with file A. An unnamed namespace is meant to be a cleaner way of achieving the same effect. The use of the global static in C++ programs for achieving internal linkage for a name is being deprecated.

3.10.6 Koenig Lookup for Unqualified Function Names

So far the reader has seen three different ways in which the namespace definition of a name can be accessed outside the namespace. We can either use a namespacequalified name, or resort to a using declaration, or to a using directive in the manner shown earlier.

For unqualified function names, there is yet another way that allows the system to access the namespace definition of a function name—through Koenig lookup.

This is demonstrated by the following program. The program defines in line (B) a new type X in namespace Module1 and then proceeds to define in line (C) a function foo( X ) in the same namespace. As shown in line (D), another namespace, Module2, also contains a function of same name and the same parameter type as the function in namespace Module1. Finally, yet another version of the same function, foo( int. ), is defined in the global scope in line (E).

As you would expect, the call to foo in line (G) in main invokes the global definition of foo. Now let us examine closely the call to foo( X ) in line (H). In the scope in which foo is invoked in line (H), there does not exist a function definition with the required parameter type that could possibly be used for the function call. Nonetheless, the compiler has no problem with the function call in line (H) because it uses what is known as Koenig lookup. Koenig lookup permits the compiler to reach into all the namespaces where the arguments types of a function are defined. If such namespaces contain function definitions that could possibly be used for a given function call, all those function definitions become candidates for possible invocation. The particular function definition chosen for invocation is selected through overload resolution discussed in Chapter 9. In the program shown, because the type of argument in the function call in line (H) is not defined in namespace Module2, the version of foo in line (D) is not considered for possible invocation.[12]

 
/Koenig.cc #include <iostream> using namespace std; namespace Module1 { //(A) class X {}; //(B) void foo( X xobj ) { cout << "Module1's foo(X) invoked"; } //(C) } namespace Module2 { void foo( Module1::X xobj ) { cout << "X's foo(X) invoked"; } //(D) } void foo( int i ) { cout << "global foo(int) invoked"; } //(E) int main() { Modulel::X xob; //(F) foo( 1 ); // global foo(int) invoked //(G) foo( xob ); // Module1's foo(X) invoked //(H) return 0; }

[10]See Section 7.7 of Chapter 7 for a discussion on the scope of an identifier in C++. As mentioned there, the scope of an identifier is that part of a program in which an identifier is recognized as declared.

[11]A block is a section of code delimited by curly brackets. Chapter 7 discusses this and other aspects of scoping in greater detail.

[12]Koenig lookup is also known as argument-dependent name lookup.




Programming With Objects[c] A Comparative Presentation of Object-Oriented Programming With C++ and Java
Programming with Objects: A Comparative Presentation of Object Oriented Programming with C++ and Java
ISBN: 0471268526
EAN: 2147483647
Year: 2005
Pages: 273
Authors: Avinash Kak

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net