Topic : ANSI C++ Namespaces
Author : McMillan
Page : << Previous 2  Next >>
Go to page :


line, the compiler detects that the object c, which is the argument of the function func(), belongs to namespace MINE. Consequently, the compiler looks at namespace MINE to locate the declaration of func(), "guessing" the programmer's intent:

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

Without Koenig lookup, namespaces impose an unacceptable tedium on the programmer, who has to either repeatedly specify the fully qualified names or use numerous using declarations. To push the argument in favor of Koenig lookup even further, consider the following example:

#include<iostream>
using std::cout;
int main()
{
  cout<<"hello";   //OK, operator << is brought into scope by Koenig lookup
  return 0;
}


The using declaration injects std::cout into the scope of main(), thereby enabling the programmer to use the nonqualified name cout. However, the overloaded << operator, as you might recall, is not a member of std::cout. It is a friend function that is defined in namespace std, and which takes a std::ostream object as its argument. Without Koenig lookup, the programmer has to write something similar to the following:


std::operator<<(cout, "hello");


Alternatively, the programmer can provide a using namespace std; directive. None of these options are desirable, however, because they clutter up code and can become a source of confusion and errors. (using directives are the least favorable form for rendering names visible in the current scope because they make all the members of a namespace visible indiscriminately). Fortunately, Koenig lookup "does the right thing" and saves you from this tedium in an elegant way.

Koenig lookup is applied automatically. No special directives or configuration switches are required to activate it, nor is there any way to turn it off. This fact has to be kept in mind because it can have surprising results in some circumstances. For example

namespace NS1
{
  class B{};
  void f;
};
void f(NS1::B);
int main()
{
  NS1::B b;
  f;  // ambiguous; NS1::f() or f(NS1::B)?
  return 0;
}


A Standard-compliant compiler should issue an error on ambiguity between NS1::f(NS1::B) and f(NS1::B). However, noncompliant compilers do not complain about the ambiguous call; they simply pick one of the versions of f(). This, however, might not be the version that the programmer intended. Furthermore, the problem might arise only at a later stage of the development, when additional versions of f() are added to the project -- which can stymie the compiler's lookup algorithm. This ambiguity is not confined to global names. It might also appear when two namespaces relate to one another -- for instance, if a namespace declares classes that are used as parameters of a class member function that is declared in a different namespace.

Namespaces in Practice
The conclusion that can be drawn from the previous examples is that namespaces, like other language features, must be used judiciously. For small programs that contain only a handful of classes and a few source files, namespaces are not necessary. In most cases, such programs are coded and maintained by a single programmer, and they use a limited number of components. The likelihood of name clashes in this case is rather small. If name clashes still occur, it is always possible to rename the existing classes and functions, or simply to add namespace later.

On the other hand, large-scale projects -- as was stated previously -- are more susceptible to name clashes; therefore, they need to use namespaces systematically. It is not unusual to find projects on which hundreds of programmers on a dozen or so development teams are working together. The development of Microsoft Visual C++ 6.0, for example, lasted 18 months, and more than 1000 people were involved in the development process. Managing such a huge project requires well documented coding policies -- and namespaces are one of the tools in the arsenal.

Namespace Utilization Policy in Large-Scale Projects
To see how namespaces can be used in configuration management, imagine an online transaction processing system of an imaginary international credit card company, Unicard. The project comprises several development teams. One of them, the database administration team, is responsible for the creation and maintenance of the database tables, indexes, and access authorizations. The database team also has to provide the access routines and data objects that retrieve and manipulate the data in the database. A second team is responsible for the graphical user interface. A third team deals with the international online requests that are initiated by the cinemas, restaurants, shops, and so on where tourists pay with their international Unicard. Every purchase of a cinema ticket, piece of jewelry, or art book has to be confirmed by Unicard before the card owner is charged. The confirmation process involves checking for the validity of the card, its expiration date, and the card owner's balance. A similar confirmation procedure is required for domestic purchases. However, international confirmation requests are transmitted via satellite, whereas domestic confirmations are usually done on the telephone.

In software projects, code reuse is paramount. Because the same business logic is used for both domestic and international confirmations, the same database access objects need to be used to retrieve the relevant information and perform the necessary computations. Still, an international confirmation also involves a sophisticated communication stack that receives the request that is transmitted via satellite, decrypts it, and returns an encrypted response to the sender. A typical implementation of satellite-based confirmation application can be achieved by means of combining the database access objects with the necessary communication objects that encapsulate protocols, communication layers, priority management, message queuing, encryption, and decryption. It is not difficult to imagine a name conflict resulting from the simultaneous use of the communication components and the database access objects.

For example, two objects -- one encapsulating a database connection and the other referring to a satellite connection -- can have an identical name: Connection. If, however, communication software components and database access objects are declared in two distinct namespaces, the potential of name clashes is minimized. Therefore, com::Connection and dba::Connection can be used in the same application simultaneously. A systematic approach can be based on allocating a separate namespace for every team in a project in which all the components are declared. Such a policy can help you avoid name clashes among different teams and third party code used in the project.

Namespaces and Version Control
Successful software projects do not end with the product's rollout. In most projects, new versions that are based on their predecessors are periodically released. Moreover, previous versions have to be supported, patched, and adjusted to operate with new operating systems, locales, and hardware. Web browsers, commercial databases, word processors, and multimedia tools are examples of such products. It is often the case that the same development team has to support several versions of the same software product. A considerable amount of software can be shared among different versions of the same product, but each version also has its specific components. Namespace aliases can be used in these cases to switch swiftly from one version to another.

Continuous projects in general have a pool of infrastructure software components that are used ubiquitously. In addition, every version has its private pool of specialized components. Namespace aliases can provide dynamic namespaces; that is, a namespace alias can point at a given time to a namespace of version X and, at another time, it can refer to a different namespace. For example

namespace ver_3_11  //16 bit
{  
  class Winsock{/*..*/};
  class FileSystem{/*..*/};
};
namespace ver_95 //32 bit
{  
  class Winsock{/*..*/};
  class FileSystem{/*..*/};
}
int main()//implementing 16 bit release
{
  namespace current = ver_3_11; // current is an alias of ver_3_11
  using current::Winsock;
  using current::FileSystem;
  FileSystem  fs; // ver_3_11::FileSystem
  //...
  return 0;
}


In this example, the alias current is a symbol that can refer to either ver_3_11 or ver_95. To switch to a different version, the programmer only has to assign a different namespace to it.

Namespaces Do not Incur Additional Overhead
Namespace resolution, including Koenig lookup, are statically resolved. The underlying implementation of namespaces occurs by means of name mangling, whereby the compiler incorporates the function name with its list of arguments, its class name, and its namespace in order to create a unique name for it (see Chapter 13, "C Language Compatibility Issues," for a detailed account of name mangling). Therefore, namespaces do not incur any runtime or memory overhead.

The Interaction of Namespaces with Other Language Features
Namespaces interact with other features of the language and affect programming techniques. Namespaces made some features in C++ superfluous or undesirable.

Scope Resolution Operator Should Not Be Used To Designate Global Names
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 (as in the following example):

void String::operator = (const String& other)
{
  ::strcpy (this->buffer, other.getBuff());
}


This practice is not recommended. Many of the standard functions that were once global are now grouped inside namespaces. For example, strcpy now belongs to namespace std, as do most of the Standard Library's functions. Preceding

Page : << Previous 2  Next >>