Day 18. Creating and Using Namespaces

Namespaces can be used to help you organize your classes. More importantly, namespaces help programmers avoid name clashes when using more than one library.

Today, you will learn

• How functions and classes are resolved by name

• How to create a namespace

• How to use a namespace

• How to use the standard namespace std

Getting Started

Name conflicts have been a source of aggravation to both C and C++ developers. A name clash happens when a duplicate name with matching scope is found in two parts of your program. The most common occurrence can be found in different library packages. For example, a container class library will almost certainly declare and implement a List class. (You’ll learn more about container classes when you learn about templates on Day 19, “Templates.”)

It is not a surprise to find a List class also being used in a windowing library. Suppose that you want to maintain a list of windows for your application. Further assume that you are using the List class found in the container class library. You declare an instance of the window library’s List to hold your windows, and you discover that the member functions you want to call are not available. The compiler has matched your List declaration to the List container in the Standard Library, but what you really wanted is the List found in the vendor-specific window library.

Namespaces are used to reduce the chance of name conflicts. Namespaces are similar in some ways to classes, and the syntax is very similar.

Items declared within the namespace are owned by the namespace. All items within a namespace have public visibility. Namespaces can be nested within other namespaces. Functions can be defined within the body of the namespace or defined outside the body of the namespace. If a function is defined outside the body of the namespace, it must be qualified by the namespace’s name or the calling program must have imported the namespace into its global namespace.

Resolving Functions and Classes by Name

As the compiler parses source code and builds a list of function and variable names, it also checks for name conflicts. Those conflicts that it can’t resolve are left for the linker to resolve.

The compiler cannot check for name clashes across object files or other translation units; that is the purpose of the linker. Thus, the compiler does not even offer a warning in those cases.

It is not uncommon for the linker to fail with the message


Identifier multiply defined

where identifier is some named type. You see this linker message if you have defined the same name with the same scope in different translation units. You get a compiler error if you redefine a name within a single file having the same scope. Listing 18.1a and Listing 18.1b are an example, when compiled and linked together, that produces an error message by the linker.

Listing 18.1A. First Listing Using integerValue

Image

Listing 18.1B. Second Listing Using integerValue


0:  // file second.cpp
1:  int integerValue = 0 ;
2:  // end of second.cpp

Image

My linker announces the following diagnostic:


in second.obj: integerValue already defined in first.obj.

If these names were in a different scope, the compiler and linker would not complain about the duplication.

It is also possible to receive a warning from the compiler concerning identifier hiding. The compiler should warn, in first.cpp in Listing 18.1a, that integerValue in main() is hiding the global variable with the same name.

To use the integerValue declared outside main(), you must explicitly scope the variable to global scope. Consider this example in Listings 18.2a and 18.2b, which assigns the value 10 to the integerValue outside main() and not to the integerValue declared within main().

Listing 18.2A. First Listing for Identifier Hiding

Image

Listing 18.2B. Second Listing for Identifier Hiding


0:  // file second.cpp
1:  int integerValue = 0 ;
2:  // end of second.cpp

Note

Note the use of the scope resolution operator ::, indicating that the integerValue being referred to is global, not local.

Image

The problem with the two global integers defined outside of any functions is that they have the same name and visibility, and, thus, cause a linker error.

Visibility of Variables

The term visibility is used to designate the scope of a defined object, whether it is a variable, a class, or a function. Although this was covered on Day 5, “Organizing into Functions,” it is worth covering again here briefly.

As an example, a variable declared and defined outside any function has file, or global, scope. The visibility of this variable is from the point of its definition through the end of the file. A variable having a block, or local, scope is found within a block structure. The most common examples are variables defined within functions. Listing 18.3 shows the scope of variables.

Listing 18.3. Working Variable Scope


0:  // Listing 18.3
1:  int globalScopeInt  = 5 ;
2:  void f( )
3:  {
4:      int localScopeInt = 10 ;
5:  }
6:  int main( )
7:  {
8:      int localScopeInt = 15 ;
9:      {
10:          int anotherLocal = 20 ;
11:          int localScopeInt = 30 ;
12:      }
13:      return 0 ;
14:  }

Image

The first int definition, globalScopeInt, on line 1 is visible within the functions f() and main(). The next definition is found on line 4 within the function f() and is named localScopeInt. This variable has local scope, meaning that it is visible only within the block defining it.

The main() function cannot access f()’s localScopeInt. When the f() function returns, localScopeInt goes out of scope. The third definition, also named localScopeInt, is found on line 8 of the main() function. This variable has block scope.

Note that main()’s localScopeInt does not conflict with f()’s localScopeInt. The next two definitions on lines 10 and 11, anotherLocal and localScopeInt, both have block scope. As soon as the closing brace is reached on line 12, these two variables lose their visibility.

Notice that this localScopeInt is hiding the localScopeInt defined just before the opening brace (the second localScopeInt defined in the program). When the program moves past the closing brace, the second localScopeInt defined resumes visibility. Any changes made to the localScopeInt defined within the braces does not affect the contents of the outer localScopeInt.

Linkage

Names can have internal and external linkage. These two terms refer to the use or availability of a name across multiple translation units or within a single translation unit. Any name having internal linkage can only be referred to within the translation unit in which it is defined. For example, a variable defined to have internal linkage can be shared by functions within the same translation unit. Names having external linkage are available to other translation units. Listings 18.4a and 18.4b demonstrate internal and external linkage.

Listing 18.4A. Internal and External Linking


0:  // file: first.cpp
1:  int externalInt = 5 ;
2:  const int j = 10 ;
3:  int main()
4:  {
5:      return 0 ;
6:  }

Listing 18.4B. Internal and External Linking


0:  // file: second.cpp
1:  extern int externalInt ;
2:  int anExternalInt = 10 ;
3:  const int j = 10 ;

Image

The externalInt variable defined on line 1 of first.cpp (Listing 18.4a) has external linkage. Although it is defined in first.cpp, second.cpp can also access it. The two js found in both files are const, which, by default, have internal linkage. You can override the const default by providing an explicit declaration, as shown in Listings 18.5a and 18.5b.

Listing 18.5A. Overriding the const Default


0:  // file: first.cpp
1:  extern const int j = 10 ;

Listing 18.5B. Overriding the const Default

Image

Note that cout is called on line 5 with the namespace designation of std. When built, this example displays the following:


j is 10

Static Global Variables

The standards committee has deprecated the use of static global variables. Static global variables are declared as follows:


static int staticInt = 10 ;
int main()
{
    //...
}

The use of static to limit the scope of external variables is no longer recommended and might become illegal in the future. You should now use namespaces instead of static. Of course, to do this, you need to know how to create one.

Image

Creating a Namespace

The syntax for a namespace declaration is similar to the syntax for a struct or class declaration: First apply the keyword namespace followed by an optional namespace name, and then an opening curly brace. The namespace is concluded with a closing brace but no terminating semicolon.

For example:

Image

The name Window uniquely identifies the namespace. You can have many occurrences of a named namespace. These multiple occurrences can occur within a single file or across multiple translation units. When this occurs, the separate instances are merged together by the compiler into a single namespace. The C++ Standard Library namespace, std, is a prime example of this feature. This makes sense because the Standard Library is a logical grouping of functionality, but it is too large and complex to be kept in a single file.

The main concept behind namespaces is to group related items into a specified (named) area. Listings 18.6a and 18.6b provide a brief example of a namespace that spans multiple header files.

Listing 18.6A. Grouping Related Items

Image

Listing 18.6B. Grouping Related Items

Image

Image

As you can see, the Window namespace is spread across both header files. The compiler treats both the move() function and the resize() function as part of the Window namespace.

Declaring and Defining Types

You can declare and define types and functions within namespaces. Of course, this is a design and maintenance issue. Good design dictates that you should separate interface from implementation. You should follow this principle not only with classes but also with namespaces. The following example demonstrates a cluttered and poorly defined namespace:

Image

You can see how quickly the namespace becomes cluttered! The previous example is approximately 20 lines in length; imagine if this namespace were four times longer.

Defining Functions Outside a Namespace

You should define namespace functions outside the namespace body. Doing so illustrates a clear separation of the declaration of the function and its definition—and also keeps the namespace body uncluttered. Separating the function definition from the namespace also enables you to put the namespace and its embodied declarations within a header file; the definitions can be placed into an implementation file. Listings 18.7a and 18.7b illustrate this separation.

Listing 18.7A. Declaring a Header in a Namespace

Image

Listing 18.7B. Declaring the Implementation in the Source File

Image

Adding New Members

New members can be added to a namespace only within the namespace’s body. You cannot define new members using qualifier syntax. The most you can expect from this style of definition is a complaint from your compiler. The following example demonstrates this error:


namespace Window
{
    // lots of declarations
}
//...some code
int Window::newIntegerInNamespace ; // sorry, can’t do this

The preceding line of code is illegal. Your (conforming) compiler issues a diagnostic reflecting the error. To correct the error—or avoid it altogether—move the declaration within the namespace body.

When you add new members, you do not want to include access modifiers, such as public or private. All members encased within a namespace are public. The following code does not compile because you cannot specify private:


namespace Window
{
    private:
          void move( int x, int y ) ;
}

Nesting Namespaces

A namespace can be nested within another namespace. The reason they can be nested is because the definition of a namespace is also a declaration. As with any other namespace, you must qualify a name using the enclosing namespace. If you have nested namespaces, you must qualify each namespace in turn. For example, the following shows a named namespace nested within another named namespace:


namespace Window
{
        namespace Pane
{
                 void size( int x, int y ) ;
       }
}

To access the function size() outside of the Window namespace, you must qualify the function with both enclosing namespace names. In this case, you need to use the following line to access size:


Window::Pane::size( 10, 20 ) ;

Using a Namespace

Let’s take a look at an example of using a namespace and the associated use of the scope resolution operator. In the example, all types and functions for use within the namespace Window are declared. After everything required is defined, any member functions that were declared are defined. These member functions are defined outside of the namespace; the names are explicitly identified using the scope resolution operator. Listing 18.8 illustrates using a namespace.

Listing 18.8. Using a Namespace

Image

Image

Image


x 20 y 20

Image

Note that class Pane on lines 7–19 is nested inside the namespace Window, which is on lines 3–20. This is the reason you have to qualify the name Pane with the Window:: qualifier.

The static variable count, which is declared in Pane on line 16, is defined as usual. Within the function Pane::size() on lines 26–32, notice that MAX_X and MAX_Y are fully qualified. This is because Pane is in scope; otherwise, the compiler issues an error diagnostic. This also holds true for the function Pane::move().

Also interesting is the qualification of Pane::x and Pane::y inside both function definitions. Why is this needed? Well, if the function Pane::move() were written like this, you would have a problem:


void Window::Pane::move( int x, int y )
{
      if( x < Window::MAX_X  &&  x > 0 )
          x = x ;
      if( y < Window::MAX_Y  &&  y > 0 )
          y = y ;
      Platform::move( x, y ) ;
}

Can you spot the issue? You probably won’t get much of an answer from your compiler; some don’t issue any kind of diagnostic message at all.

The source of the problem is the function’s arguments. Arguments x and y hide the private x and y instance variables declared within class Pane. Effectively, the statements assign both x and y to itself:


x = x ;
y = y ;

The using Keyword

The using keyword is used for both the using directive and the using declaration. The syntax of the using keyword determines whether the context is a directive or a declaration.

The using Directive

The using directive effectively exposes all names declared in a namespace to be in the current scope. You can refer to the names without qualifying them with their respective namespace name. The following example shows the using directive:


namespace Window
{
        int value1 = 20 ;
        int value2 = 40 ;
}
. . .
Window::value1 = 10 ;

using namespace Window ;
value2 =  30 ;

The scope of the using directive begins at its declaration and continues on to the end of the current scope. Notice that value1 must be qualified to reference it. The variable value2 does not require the qualification because the directive introduces all names in a namespace into the current scope.

The using directive can be used at any level of scope. This enables you to use the directive within block scope; when that block goes out of scope, so do all the names within the namespace. The following example shows this behavior:


namespace Window
{

        int value1 = 20 ;
        int value2 = 40 ;
}
//. . .
void f()
{
     {
            using namespace Window ;
            value2 = 30 ;
     }
     value2 = 20 ; //error!
}

The final line of code in f(), value2 = 20 ; is an error because value2 is not defined. The name is accessible in the previous block because the directive pulls the name into that block. When that block goes out of scope, so do the names in namespace Window. For value2 to work in the final line, you need to fully qualify it:


Window::value2 = 20 ;

Variable names declared within a local scope hide any namespace names introduced in that scope. This behavior is similar to how a local variable hides a global variable. Even if you introduce a namespace after a local variable, that local variable hides the namespace name. Consider the following example:


namespace Window
{
        int value1 = 20 ;
        int value2 = 40 ;
}
//. . .
void f()
{
       int value2 = 10 ;
       using namespace Window ;
       std::cout << value2 << std::endl  ;
}

The output of this function is 10, not 40. The value2 in namespace Window is hidden by the value2 in f(). If you need to use a name within a namespace, you must qualify the name with the namespace name.

An ambiguity can arise using a name that is both globally defined and defined within a namespace. The ambiguity surfaces only if the name is used, not just when a namespace is introduced. This is demonstrated with the following code fragment:


namespace Window
{
        int value1 = 20 ;
}
//. . .
using namespace Window ;
int value1 = 10 ;
void f( )
{
      value1 = 10 ;
}

The ambiguity occurs within function f(). The directive effectively brings Window::value1 into the global namespace; because a value1 is already globally defined, the use of value1 in f() is an error. Note that if the line of code in f() were removed, no error would exist.

The using Declaration

The using declaration is similar to the using directive except that the declaration provides a finer level of control. More specifically, the using declaration is used to declare a specific name (from a namespace) to be in the current scope. You can then refer to the specified object by its name only. The following example demonstrates the use of the using declaration:


namespace Window
{
        int value1 = 20 ;
        int value2 = 40 ;

        int value3 = 60 ;
}
//. . .
using Window::value2 ; //bring value2 into current scope
Window::value1 = 10 ;  //value1 must be qualified
value2 = 30 ;
Window::value3 = 10 ;  //value3 must be qualified

The using declaration adds the specified name to the current scope. The declaration does not affect the other names within the namespace. In the previous example, value2 is referenced without qualification, but value1 and value3 require qualification. The using declaration provides more control over namespace names that you bring into scope. This is in contrast with the directive that brings all names in a namespace into scope.

After a name is brought into a scope, it is visible until the end of that scope. This behavior is the same as any other declaration. A using declaration can be used in the global namespace or within any local scope.

It is an error to introduce a duplicate name into a local scope in which a namespace name has been declared. The reverse is also true. The following example shows this:


namespace Window
{
        int value1 = 20 ;
        int value2 = 40 ;
}
//. . .
void f()
{
   int value2 = 10 ;
   using Window::value2 ; // multiple declaration
   std::cout << value2 << std::endl  ;
}

The second line in function f() produces a compiler error because the name value2 is already defined. The same error occurs if the using declaration is introduced before the definition of the local value2.

Any name introduced at local scope with a using declaration hides any name outside that scope. Consider the following code snippet:


namespace Window
{
        int value1 = 20 ;
        int value2 = 40 ;
}
int value2 = 10 ;
//. . .
void f()
{

    using Window::value2 ;
    std::cout << value2 << std::endl  ;
}

The using declaration in f() hides the value2 defined in the global namespace.

As mentioned before, a using declaration gives you finer control over the names introduced from a namespace. A using directive brings all names from a namespace into the current scope. It is preferable to use a declaration over a directive because a directive effectively defeats the purpose of the namespace mechanism. A declaration is more definitive because you are explicitly identifying the names you want to introduce into a scope. A using declaration does not pollute the global namespace, as is the case with a using directive (unless, of course, you declare all names found in the namespace). Name hiding, global namespace pollution, and ambiguity all are reduced to a more manageable level by using the using declaration.

The Namespace Alias

A namespace alias is designed to provide another name for a named namespace. An alias provides a shorthand term for you to use to refer to a namespace. This is especially true if a namespace name is very long; creating an alias can help cut down on lengthy, repetitive typing. Consider the following code:


namespace the_software_company
{
        int value ;
        // . . .
}
the_software_company::value = 10 ;
. . .
namespace TSC = the_software_company ;
TSC::value = 20 ;

A drawback, of course, is that your alias might collide with an existing name. If this is the case, the compiler catches the conflict and you can resolve it by renaming the alias.

The Unnamed Namespace

An unnamed namespace is simply that—a namespace that does not have a name. A common use of unnamed spaces is to shield global data from potential name clashes between object files and other translation units. Every translation unit has a unique, unnamed namespace. All names defined within the unnamed namespace (within each translation unit) can be referred to without explicit qualification. Listings 18.9a and 18.9b are examples of two unnamed namespaces found in two separate files.

Listing 18.9a. An Unnamed Namespace


0:  // file: one.cpp
1:  namespace
2:  {
3:      int value ;
4:      char p( char *p ) ;
5:      //. . .
6:  }

Listing 18.9b. A Second Unnamed Namespace


0:  // file: two.cpp
1:  namespace
2:  {
3:      int value ;
4:      char p( char *p ) ;
5:      //. . .
6:  }
7:  int main( )
8:  {
9:      char c = p( char * ptr ) ;
10:  }

Image

In the case in which these two listings are compiled into the same executable, each of the names, value and function p(), is distinct to its respective file. To refer to a (unnamed namespace) name within a translation unit, use the name without qualification. This usage is demonstrated in the previous example with the call to function p() within each file.

This use implies a using directive for objects referred to from an unnamed namespace. Because of this, you cannot access members of an unnamed namespace in another translation unit.

The behavior of an unnamed namespace is the same as a static object having external linkage. Consider this example:


static int value = 10 ;

Remember that this use of the static keyword is deprecated by the standards committee. Namespaces now exist to replace code as this static declaration. Another way to think of unnamed namespaces is that they are global variables with internal linkage.

The Standard Namespace std

The best example of namespaces is found in the C++ Standard Library. The Standard Library is completely encased within the namespace named std. All functions, classes, objects, and templates are declared within the namespace std.

You have seen code such as the following:


#include <iostream>
using namespace std ;

Now, you know that when you use the using directive in this manner that it is pulling everything in from the named namespace.

Going forward, you should consider it bad form to employ the using directive when using the Standard Library. Why? Because doing so pollutes the global namespace of your applications with all the names found in the header. Keep in mind that all header files use the namespace feature, so if you include multiple standard header files and specify the using directive, then everything declared in the headers is in the global namespace.

You might be noting that most of the examples in this book violate this rule; this action is not an intent to advocate violating the rule, but it is used for brevity of the examples. You should use the using declaration instead, as in Listing 18.10.

Listing 18.10. The Correct Way to Use std Namespace Items


0:  #include <iostream>
1:  using std::cin ;
2:  using std::cout ;
3:  using std::endl ;
4:  int main( )
5:  {
6:      int value = 0 ;
7:      cout << "So, how many eggs did you say you wanted?" << endl ;
8:      cin >> value ;
9:      cout << value << " eggs, sunny-side up!" << endl ;
10:      return( 0 ) ;
11:  }

Image


So, how many eggs did you say you wanted?
4
4 eggs, sunny-side up!

Image

As you can see, three items from the std namespace are used. These are declared on lines 1–3.

As an alternative, you could fully qualify the names that you use, as in Listing 18.11.

Listing 18.11. Qualifying Namespace Items Inline


0:  #include <iostream>
1:  int main( )
2:  {
3:      int value = 0 ;
4:      std::cout << "How many eggs did you want?" << std::endl ;
5:      std::cin >> value ;
6:      std::cout << value << " eggs, sunny-side up!" << std::endl ;
7:      return( 0 ) ;
8:  }

Image


How many eggs did you want?
4
4 eggs, sunny-side up!

Image

Qualifying namespace items inline might be appropriate for shorter programs but can become quite cumbersome for any significant amount of code. Imagine having to prefix std:: for every name you use that is found in the Standard Library!

Summary

Today’s lesson expanded on information you have been previously exposed to throughout this book.

Creating a namespace is very similar to a class declaration. A couple of differences are worth noting. First, a semicolon does not follow a namespace’s closing brace. Second, a namespace is open, whereas a class is closed. This means that you can continue to define the namespace in other files or in separate sections of a single file.

Anything that can be declared can be inserted into a namespace. If you are designing classes for a reusable library, you should be using the namespace feature. Functions declared within a namespace should be defined outside of that namespace’s body. This promotes a separation of interface from implementation and also keeps the namespace from becoming cluttered.

The using directive is used to expose all names in a namespace into the current scope. This effectively fills the global namespace with all names found in the named namespace. It is generally bad practice to use the using directive, especially with respect to the Standard Library. Use using declarations instead.

A using declaration is used to expose a specific namespace item into the current scope. This allows you to refer to the object by its name only.

A namespace alias is similar in nature to a typedef. A namespace alias enables you to create another name for a named namespace. This can be quite useful when you are using a namespace with a long name or nested namespaces.

Every file can contain an unnamed namespace. An unnamed namespace, as its name implies, is a namespace without a name. An unnamed namespace allows you to use the names within the namespace without qualification. It keeps the namespace names local to the translation unit. Unnamed namespaces are the same as declaring a global variable with the static keyword.

Q&A

Q   Do I have to use namespaces?

A   No, you can write simple programs and ignore namespaces altogether. Be certain to use the old Standard Libraries (for example, #include <string.h>) rather than the new libraries (for example, #include <cstring>. Because of the reasons you learned in today’s lesson, it is recommended that you do use namespaces.

Q   Is C++ the only language that uses namespaces?

A   No. Other languages also use namespaces to help organize and separate values. This includes languages such as Visual Basic 7 (.NET), C#, and more. Other languages have similar concepts. For example, Java has packages.

Q   What are the unnamed namespaces? Why do I need unnamed namespaces?

A   Unnamed namespaces are namespaces without names. They are used to wrap a collection of declarations against possible name clashes. Names in an unnamed namespace cannot be used outside of the translation unit where the namespace is declared.

Workshop

The Workshop contains quiz questions to help solidify your understanding of the material covered and exercises to provide you with experience in using what you’ve learned. Try to answer the quiz and exercise questions before checking the answers in Appendix D, and be certain you understand the answers before going to tomorrow’s lesson.

Quiz

1. How do you access the function MyFunc() if it is in the Inner namespace within the Outer namespace?

Outer::Inner::MyFunc();

2. Consider the following code:


int x = 4;
int main()
{
    for( y = 1; y < 10; y++)
    {
        cout << y << ":" << endl;
        {
            int x = 10 * y;
            cout << "X = " << x << endl
        }
    }
    // *** HERE ***
}

What is the value of X when this program reaches “HERE” in the listing?

3. Can I use names defined in a namespace without using the using keyword?

4. What are the major differences between normal and unnamed namespaces?

5. What are the two forms of statements with the using keyword? What are the differences between those two forms?

6. What are the unnamed namespaces? Why do we need unnamed namespaces?

7. What is the standard namespace?

Exercises

1. BUG BUSTERS: What is wrong in this program?


0:  #include <iostream>
1:  int main()
2:  {
3:        cout << "Hello world!" << endl;
4:        return 0;
5:  }

2. List three ways of fixing the problem found in Exercise 1.

3. Show the code for declaring a namespace called MyStuff. This namespace should contain a class called MyClass.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.93.12