home account info subscribe login search FAQ/help site map contact us


 
Brief Full
 Advanced
      Search
 Search Tips
[an error occurred while processing this directive]
Previous Table of Contents Next


Part VII
Appendixes

APPENDIX A
NAMESPACES

Being a member of the C++ standardization committee, it’s not hard to guess what my opinion is. Seriously, Java and C++ differ radically from one another in many respects. One of them is compiler writing. Bjarne Stroustrup addresses this issue: “the overall principle [is] that where there is a choice between inconveniencing compiler writers and annoying users, the compiler writers should be inconvenienced” (The Evolution of C++, ISBN 026273107x, page 50). My experience with Java has led me to conclude that its designers adopted the opposite approach: simple compiler writing at the cost of programmer’s drudgery. Some examples of this are the exclusion of operator overloading, enum types, and default arguments. They do not incur overhead of any kind, and no one doubts their importance and usefulness. Yet they make a compiler writer’s work more difficult.

Namespaces are very different from packages. Both provide a mechanism for name clashing prevention, but remember that namespaces allow a fine-grained control on the extent to which declarations are injected to a scope. A using-declaration enables the user to inject only a single constituent, and a fully qualified name is even finer. Koenig lookup is also meat to ease the life of a programmer. No equivalents exist in Java.

Namespaces were introduced to the C++ Standard in 1995. This appendix will explain what namespaces are, how and when they should be used, and why they were added to the language. Finally, this appendix will discuss the way namespaces interact with other language features.

THE RATIONALE BEHIND NAMESPACES

To understand why namespaces were added to the language in the first place, let’s use an analogy. Imagine that the file system on your computer didn’t have directories and subdirectories at all. All files would be stored in a flat repository, visible all the time to every user and application. As a consequence, extreme difficulties would arise. Filenames would clash (with some systems limiting a filename to 8 characters and 3 more characters for the extension, this is likely to happen); simple actions such as listing, copying, and 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 clashes, they allow 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 making its way as a general-purpose programming language, many vendors were shipping proprietary implementations of various component classes. Class libraries for string manipulations, mathematical functions, and 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, could appear both in a mathematical library and a separate container library that were used at the same time. It was impossible for the compiler and the linker to distinguish between the identical names of two different classes.

LARGE-SCALE PROJECTS ARE SUSCEPTIBLE TO NAME CLASHES

Name clashes are not confined to vendors’ frameworks. In large-scale software projects, short and elegant names for types and functions can also cause name conflicts because the same name might be used more than once to denote different entities. In the pre-namespace era, the only workaround was to use various affixes in identifiers’ names. This practice, however, is tedious and error prone:

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 allow you to use convenient, short, and intelligible names safely. Instead of repeating the unwieldy affixes time after time, you can group your classes and functions in a namespace and factor out the recurring affix like this:

//file excelSoftCompany.h
namespace excelSoftCompany { // a namespace definition

        class string {/*..*/};
        class vector {/*..*/};
}

PROPERTIES OF NAMESPACES

Namespaces are more than just name containers. They were designed to allow fast, simple, and efficient migration of legacy code without inflicting overhead of any kind. Namespaces have several properties that facilitate their usage.

A FULLY QUALIFIED NAME

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. The fully qualified name of an identifier consists of its namespaces(s), followed by scope resolution operator, then its class name, and finally, the identifier itself. Because both namespaces and classes can be nested, the resulting name can be rather long, yet it ensures unique identification:

size_t maxPossibleLength =
  std::string::npos;  //a fully qualified name. npos is a member
of string; string  belongs to namespace std

However, repeating the fully qualified name is tedious and less readable. In general, a using-declaration or a using-directive is preferred.

A USING-DECLARATION AND A USING-DIRECTIVE

A using-declaration consists of the keyword using followed by 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:

#include <vector>  //STL vector; belongs to namespace std
void main()
{
   using std::vector;  //using declaration; every occurrence of
vector is looked up in std
   vector <int> vi; // instead of std::vector<int>
}

A using-directive, on the other hand, instructs the compiler to recognize all members of a namespace and not just one. 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
void main()
{
  using namespace std; // a using-directive; all <iostream> and
<vector> declarations  now accessible
  vector  <int> vi;
  vi.push_back(10);
  cout<<vi[0];
}

Let’s 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 access your own string class as well as the ANSI string class in the same program now.

#include <string> //ANSI string class
#include “excelSoftCompany.h”

void main()
{
using namespace excelSoftCompany;
string s; //referring to class excelSoftCompany::string

std::string standardstr; //now instantiate an ANSI string object
}

NAMESPACES ARE OPEN

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 { //now extending MyProj namespace
        class RealTimeEncoder{ public: NetProtocols detect()
{return UDP;}
                                    //other class members
                           };

        class NetworkLink {};

        class UserInterface {};
}

In a separate file, the same namespace can be extended with additional declarations.

The complete namespace can be extracted from both files like this:

  //file app.cpp

#include “proj_const.h”
#include “proj_classes.h”

void main() {
    using namespace MyProj;
    RealTimeEncoder encoder;
    NetProtocols protocol = encoder.detect();

}//end main

NAMESPACE ALIASES

Choosing a short name for a namespace can eventually lead to a name clash. Yet very long namespaces are not easy to use. For this purpose, namespace aliases can be used. The following sample defines an alias, ESC, of the unwieldy Excel_Software_Company namespace.

//file decl.h
namespace Excel_Software_Company {
class Date {/*..*/};
class Time {/*..*/};
}

//file calendar.cpp
#include “decl.h”
void main()
{
namespace ESC = Excel_Software_Company; //ESC is an alias for
Excel_Software_Company
ESC::Date date;
ESC::Time time;
}

KOENIG LOOKUP

Andrew Koenig, one of the forefathers of C++, devised an algorithm for resolving namespace members’ lookup. This algorithm is used in all Standard-conforming compilers to handle cases like the following:

namespace MINE {
  class C {};
  void func(C);
}

MINE::C c; // global object of type MINE::C

void main() {
  func( c ); // OK, MINE::f called
}

No using-declaration or using-directive exists in the program. Still, the compiler did the right thing by applying Koenig lookup. How does Koenig lookup work?

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. Thus, in the following source line the compiler detects that the object c, which is the argument of the function f, belongs to namespace MINE. Consequently, the compiler looks at namespace MINE to locate the declaration of f.

func( c ); // OK, MINE::f called

NAMESPACES DO NOT INCUR ADDITIONAL OVERHEAD

The underlying implementation of namespaces is by means of name mangling. The compiler incorporates the function name with its list of arguments, class name, and namespace in order to create a unique name for it. Therefore, namespaces do not incur runtime or memory overhead.

THE INTERACTION OF NAMESPACES WITH OTHER LANGUAGE FEATURES

Namespaces affect other features of the language as well as programming techniques. For example, namespaces make some features superfluous or undesirable.

:: OPERATOR SHOULD NOT BE USED TO DESIGNATE A GLOBAL FUNCTION

In some frameworks (MFC, for instance), it is customary to add the scope resolution operator, ::, before a global function’s name to mark it explicitly as a function that is not a class member. For example:

void String::operator = (const String& other)
{
 ::strcpy (this->buffer, &other); // strcpy is preceded by ::
operator, not a good idea
}

This practice is not recommended anymore. Many of the standard functions that used to be global are now grouped under namespaces. For example, strcpy now belongs to namespace std, as do most of the Standard library’s functions. Preceding these functions with the scope resolution operator will stymie the lookup algorithm of the compiler, resulting in compilation errors. Therefore, it’s advisable to use the function’s name without the scope resolution operator.

TURNING AN EXTERNAL FUNCTION INTO A FILE-LOCAL FUNCTION

In standard C, a function declared as static has an internal linkage; it is accessible only from within the translation unit (source file) in which it is declared. This technique is used to support information hiding, such as in the following sample:

    //File hidden.c

static void decipher(FILE *f);//function accessible only from
within this file

    //now use this function in the current source file

decipher (“passwords.bin”);

    //end of file

Though still supported in C++, this convention is now considered a deprecated feature, which means that future releases of your compiler might issue a warning message when finding a static function that is not a member of a class. To make a function accessible only from within the translation unit in which it is declared, you should use a nameless namespace instead. You might do that, for instance, when you migrate C code. The following example demonstrates this technique:

//File hidden.cpp

namespace { //nameless
  void decipher(FILE *f); //accessible only from within this file
}

  //now use the function in the current source file. No ‘using’
declarations or directives are needed.
decipher (“passwords.bin”);

It is guaranteed that nameless namespaces in different source files are unique. If you declare another function with the same name in a nameless namespace of another file, the two functions are hidden from one another and their names do not clash.

STANDARD HEADERS NAMES

All Standard C++ header files now have to be #included in the following way:

#include <iostream> //note: no “.h” extension

That is, the .h extension is omitted. This convention applies to the Standard C header files as well, with the addition of the letter c affixed to their name. A C Standard header formerly named <xxx.h> is now <cxxx>. For example:

#include <cassert> //formerly: <assert.h> note the prefix ‘c’
and the omission of  “.h”

The older convention for C headers, <xxx.h>, is still supported, but is now considered deprecated and should not be used in new C++ code. The reason is that C <xxx.h> headers would inject their declarations into the global namespace. In C++, most standard declarations are grouped under namespace std, and so are the <cxxx> Standard C headers. No inference should be drawn from the actual name convention used on the physical location of a header file or its underlying name. In fact, most implementations share a single physical file for both the <xxx.h> and its corresponding <cxxx> notation. This is feasible due to some under-the-hood preprocessor tricks. This convention eliminates name conflicts that might occur when global declarations are used. Remember that you must have a using-declaration, a using-directive, or a fully qualified name in order to access the declarations in the new-style standard headers:

#include <cstdio>
using namespace std; //using directive

void f(){
        printf (“Hello World\n”);
}

RESTRICTIONS ON NAMESPACES

The C++ Standard defines several restrictions on the use of namespaces. These restrictions are meant to avert anomalies or ambiguities that would create havoc in the language.

NAMESPACE STD MAY NOT BE MODIFIED

Generally, namespaces are open. It is perfectly legal to expand existing namespaces with additional declarations and definitions across several files. The only exception to the rule is namespace std. According to the Standard, the result of modifying namespace std with additional declarations—let alone a removal of existing ones—yields undefined behavior and should therefore be avoided. This restriction might seem arbitrary, but it’s just common sense—any attempt to tamper with namespace std undermines the very concept of a namespace dedicated exclusively to standard declarations.

USER-DEFINED NEW AND DELETE CANNOT BE DECLARED IN A NAMESPACE

The Standard prohibits declarations of new and delete operators in a namespace. To see why, consider the following example:

char *pc; //global
namespace A {
  void operator new ( size_t );
  void operator delete ( void * );

  void func ()
  {
    pc = new char ( ‘a’); //using A::new
  }
}

void f() { delete pc; } // which version of delete to call,
A::delete or standard delete?

Some programmers expect the operator A::delete to be selected because it matches the operator new that was used to allocate the storage. Others expect the standard operator delete to be called because A::delete is not visible in function f. By prohibiting declarations of new and delete in a namespace, this hassle is avoided.

COMMENTS

Namespaces were the latest addition to the C++ Standard. Therefore, not all existing compilers support this feature yet. However, all compiler vendors will provide namespace-supporting compilers in the near future. The importance of namespaces cannot be over-emphasized. As you have seen, any non-trivial C++ program uses components of the Standard Template Library, the iostream library, and other standard header files—all of which are now declared in namespace std.

C++ offers three methods for injecting a namespace constituent into the current scope. The first is a using-directive, which injects all of the members of a namespace into the current scope. The second is a using-declaration, which is more selective and enables the injection of a single component from a namespace. Finally, a fully qualified name uniquely identifies a namespace member. However, namespaces provide more than a mechanism for the prevention of name clashing; they can streamline the process of version control by assigning different namespaces to a namespace alias. In addition, the argument-dependent lookup, or Koenig lookup, captures the programmer’s intention without forcing him or her to use explicit references to a namespace.


Previous Table of Contents Next


Products |  Contact Us |  About Us |  Privacy  |  Ad Info  |  Home

Use of this site is subject to certain Terms & Conditions, Copyright © 1996-1999 EarthWeb Inc.
All rights reserved. Reproduction whole or in part in any form or medium without express written permision of EarthWeb is prohibited.