Packages in C++: Namespaces

A package is a named collection of declarations that may span several files (see chapter 1). A package defines the scope of the declarations it contains and may be separated into an interface package and an implementation package.

C++ provides several types of scopes: global, file, class, and block. We can also create a package scope using a namespace declaration:

namespace NAME { DECLARATION ... }

References to names outside of their namespace must be qualified using the scope resolution operator. For example, assume a function named jupiter() is declared in a namespace called Planet:[1]

namespace Planet
{
   void jupiter() { ... }
   // etc.
}

Clients of the Planet namespace must qualify calls to jupiter():

Planet::jupiter();

By contrast:

namespace RomanGods
{
   void jupiter() { ... }
   // etc.
}

Clients of the RomanGods namespace must qualify calls to jupiter():

RomanGods::jupiter();

All of the declarations in the standard C++ library are contained in a namespace called std. This means names defined in the <iostream> header file, such as ostream, istream, cout, and cin must be qualified by "std::" when they are used by clients (see Programming Note A.3). For example, here's the official version of the Hello world program:

#include <iostream>
int main()
{
   std::cout << "Hello, world!\n";
   return 0;
}

We will see how to avoid the std qualifier below.

Example 1: Namespace declarations can span multiple header files

In this example the Car namespace is declared in files ford.h and buick.h:

// ford.h
namespace Car
{
   struct Ford
   {
      void start();
      void drive();
      void stop();
   };
   void test(Ford c);
}

// buick.h
namespace Car
{
   struct Buick
   {
      void start();
      void drive();
      void stop();
   };
   void test(Buick c);
}

The linker isn't upset by what appears to be two separate declarations of the Car namespace. Instead, it regards the second "declaration" as a continuation of the first.

The implementations are in files ford.cpp and buick.cpp. Notice that the names of the member functions are qualified by the namespace name and the class name:

// buick.cpp
#include "buick.h"
#include <iostream>
void Car::Buick::start() { std::cout << "Starting a Buick\n"; }
void Car::Buick::drive() { std::cout << "Driving a Buick\n"; }
void Car::Buick::stop() { std::cout << "Stopping a Buick\n"; }
void Car::test(Buick c) { c.start(); c.drive(); c.stop(); }

// ford.cpp
#include "ford.h"
#include <iostream>
void Car::Ford::start() { std::cout << "Starting a Ford\n"; }
void Car::Ford::drive() { std::cout << "Driving a Ford\n"; }
void Car::Ford::stop() { std::cout << "Stopping a Ford\n"; }
void Car::test(Ford c) { c.start(); c.drive(); c.stop(); }

The test drivers are declared in file client.cpp:

// client.cpp
#include "ford.h"
#include "buick.h"

test1() uses the qualified names for all items declared in the Car namespace:

void test1()
{
   Car::Buick b;
   Car::Ford f;
   Car::test(b);
   Car::test(f);
}

test2() imports the name Car::test into its scope with a using declaration:

void test2()
{
   using Car::test;
   Car::Buick b;
   Car::Ford f;
   test(b);
   test(f);
}

The using declaration is a bit like an import declaration, but it only allows the unqualified use of the test() functions from the point of the declaration to the end of the scope in which the declaration occurs. In this case the declaration occurs in a block scope. In other words, the test1() function must still qualify calls to test() with "Car::", even if it is declared after test2(). Also notice that we can't import one variant of the test() function but not the other.

test3() imports the entire Car namespace into its scope with the using directive:

void test3()
{
   using namespace Car;
   Buick b;
   Ford f;
   test(b);
   test(f);
}

Like the using declaration, the using directive only allows unqualified references to namespace names within the scope in which it occurs.

It is common to import the entire std namespace into a file scope to avoid the need to qualify every reference to a library type or object. Here's our new implementation of the Hello world program:

#include <iostream>
using namespace std;
int main()
{
   cout << "Hello, world!\n";
   return 0;
}

This using directive has file scope, which means that standard library names can only be used without qualification within the file.

Example 2: Composition and Selection

Assume FarmJobs and OfficeJobs are namespaces:

namespace FarmJobs
{
   void plant() { cout << "planting corn\n"; }
   void grow() { cout << "growing corn\n"; }
   void pick() { cout << "picking corn\n"; }
}

namespace OfficeJobs
{
   void file() { cout << "filing reports\n"; }
   void type() { cout << "typing reports\n"; }
   void shred() { cout << "shredding reports\n"; }
}

We can create new namespaces from these using composition and selection. Composition creates a namespace by joining several existing namespaces:

namespace Jobs
{
   using namespace FarmJobs;
   using namespace OfficeJobs;
}

Selection creates a new namespace by selecting items from existing namespaces:

namespace EasyJobs
{
   using FarmJobs::grow;
   using OfficeJobs::file;
   using OfficeJobs::shred;
}

A client can use Jobs and EasyJobs without knowledge of FarmJobs or OfficeJobs:

void test5()
{
   Jobs::plant();
   Jobs::grow();
   Jobs::pick();
   Jobs::type();
   Jobs::file();
   Jobs::shred();
   EasyJobs::grow();
   EasyJobs::shred();
}

Example 3: Namespaces as Interfaces

Like a header file, a namespace specifies an interface to clients and implementers. The implementer's interface contains all of the declarations:

// register.h
namespace Register // cash register functions
{
   void recordItem();
   void computeTotal();
   void makeReceipt();
   void reset();
   void lock();
   void unlock();
}

As usual, we separate the implementation from the interface:

// register.cpp
#include "register.h"
void Register::recordItem() { ... }
void Register::computeTotal() { ... }
void Register::makeReceipt() { ... }
void Register::reset() { ... }
void Register::lock() { ... }
void Register::unlock() { ... }

Assume we anticipate two types of clients: clerks and managers. Using selection, we can create client interfaces for each one:

// clerk.h
#include "register.h"
namespace ClerkInterface
{
   using Register::recordItem;
   using Register::computeTotal;
   using Register::makeReceipt;
   using Register::reset;
}

// manager.h
#include "register.h"
namespace ManagerInterface
{
   using Register::unlock;
   using Register::lock;
   using Register::reset;
}

Example 4: Delegation between Namespaces[2]

We can eliminate the dependency of the manager interface, for example, on the register interface by providing implementations of the manager interface functions that delegate to the corresponding register functions:

// manager.h
namespace ManagerInterface
{
   void unlock();
   void lock();
   void reset();
}

Of course the implementation of the manager interface still depends on the register interface:

// manager.cpp
#include "manager.h"
#include "register.h"
void ManagerInterface::unlock() { Register::unlock(); }
void ManagerInterface::reset() { Register::reset(); }
void ManagerInterface::lock() { Register::lock(); }

 



[1] Note that namespace declarations don't require a terminating semicolon like class declarations do. Why?

[2] Delegation is the subject of chapter 4.