Namespaces

A large program may have thousands of functions, and it can become difficult to tell them apart. No matter how well you name them, they will get mixed up. This is, of course, not just a problem with programs; the phone book of even a small town would be impossible to use if people did not have surnames. Programmers often use naming conventions to keep functions separate (for example, stack operations would be called stack_pop(), stack_push(), and so on) but are not always consistent. Namespaces are a way of grouping functions and variables together, in much the same way that families share the same surname.

The Global Namespace

A global declaration is visible throughout a program. Although they are not necessarily evil, global variables can be a problem because distant parts of the program become coupled together. Global side effects make it possible for subsystems to interfere with each other in ways that are not obvious. You could have two files, neither of which call each other but that still interact because they operate on the same global data. Another problem is if you have thousands of global declarations, they will start 'colliding' with each; it becomes hard to come up with a unique meaningful name that isn't less than 20 characters long. The set of all global symbols is called the global namespace.

Therefore, you generally want to keep data private within files. But even if they are not declared globally, variables declared at the file scope can interfere with each other. Here is a simple (and pointless) program that consists of two files, which both contain the names jo and fred:

// one.cpp
int fred = 999;
int jo()   {  return fred; }
int main() {  return jo(); }
// two.cpp
int fred = 648;
int jo()   {  return fred; }

C:olshucwchap4>c++ one.cpp two.cpp
C:....o(.text+0x0):two.cpp: multiple definition of 'jo(void)'
C:..8xfb.o(.text+0x0):one.cpp: first defined here
C:..8i2hfb.o(.data+0x0):two.cpp: multiple definition of 'fred'
C:..8xfb.o(.data+0x0):one.cpp: first defined here

Each file is compiled correctly, but the linker complains of multiple definitions. It is a very good thing that the compile failed; I can attest from personal experience that this is one hard fault to debug! It is useful to insist that variables and functions be genuinely local to a file. You can do this by using a nameless namespace (or an anonymous namespace), as in the following example, which shows two.cpp again:


// two.cpp
namespace {
  int fred = 648;

  int jo() {  return fred; }
}

Now when one.cpp and two.cpp are compiled and linked, there is no error: fred and jo() are completely private to two.cpp. The ideal situation is for each file to be as independent as possible. (C programmers used to do this with the static attribute, but this is now officially considered old-fashioned and “depreciated,” which means the International Standard Organization [ISO] C++ standards committee wishes it would go away.)

Keeping Similar Functions Together in a Namespace

Say you are using a module to implement a stack. The implemention is easily done with an array and an index, but it is a good idea to provide a set of functions that manipulate the representation. There are two operations that modify the stack: push(), which puts a new value onto the stack, and pop(), which takes a value off the stack. It is also necessary to know how many items have been put on the stack: depth() and empty().

That is, it's not generally a good idea to write arr[iptr++]=val every time you want to push something onto the stack. It's easy to get it wrong (I usually got confused between ++iptr and iptr++), and perhaps one day we will want to implement the stack using a list.

You can then use an explicit namespace to group the functions together. The following example has an implementation file stack.cpp and an interface file stack.h:


// stack.cpp
namespace {   // private to this module
  const int STACKSIZE = 100;
  double arr[100];
  int iptr = 0;
}
namespace Stack {
  void   push(double v) {  arr[iptr++] = v;    }
  double pop()          {  return arr[--iptr]; }
  int    depth()        {  return iptr;        }
  bool   empty()        {  return depth()==0;  }
}

//stack.h
namespace Stack {
  void   push(double v);
  double pop();
  int    depth();
  bool   empty();
}

After loading this file, you can access its functions by giving the fully qualified name, by using the scope operator (that is, ::). Notice that the function empty() is defined in terms of depth(), which doesn't need to be qualified because it belongs to the same family (that is, it is within the same scope). If a family consisted of Jane Smith, John Smith, and Peter Smith, then it would be unnecessary (although not incorrect) for John to call Peter by his full name, Peter Smith. To use this module I must load the source into the system with #l, and I can then use the stack operations using their qualified names:

;> #l stack.cpp
;> Stack::push(1.2);
;> Stack::push(3.2);
;> Stack::depth();
(int) 2
;> Stack::pop();
(double) 3.2

You may find them irritating to type at first, but fully qualified names tell precisely where a function comes from, and they therefore make it easier to understand large programs. Remember that the purpose of namespaces is to organize programs better for humans (computers don't care about keeping track of thousands of names). A person reading your code should be able to tell where every name is defined.

You can make a whole namespace available globally by using the using directive; in this case, for example, you would say that the namespace Stack has been injected into the global namespace:

;> using namespace Stack;
;> push(3.4);
;> empty();
(bool) false

It's as if a whole family of functions is introduced to the system at once. This is particularly useful when you're working interactively.

The std Namespace

Just about every simple C++ program includes using namespace std:

...
namespace std { .
  ....
}

The entire standard library is defined inside std; that is, all the definitions within <iostream>, <string>, and so on are included in a std namespace definition.

An important feature of namespaces is that they are always open, which means they can always be added to. Each std namespace definition adds its functions and types to std. With C++, programmers can explicitly qualify each name from the standard library, or they can make the definitions globally available by using using namespace std. Here is a simple program in which each name is explicitly qualified with std using the scope operator (::):


// strict.cpp
#include <iostream>
#include <string>

int main() {
  std::string s;
  while (std::cin >> s) std::cout << s << std::endl;
  return 0;
}

I find this method a bit fussy and pedantic. After all, we know string, cin, cout, and endl well by now and have earned the right to call them by their first names. However, some people feel strongly that using namespace std causes namespace pollution because everything is dumped into the global namespace, which is what namespaces were designed to prevent. You need to understand the implications of using namespace std, and you need to recognize that there is one case where it is always a bad idea. The header file for the count.cpp file defined early has only one prototype, but it must include <iostream> for std::istream. That way, any programmer who uses count.h doesn't have to remember that <iostream> must be included before count.h. (You do not have to worry about the programmer then including <iostream> twice; it is simply ignored the second time.) This is a case where you don't just bring in the entire std namespace with using namespace std; it is considered particularly bad manners because it means programmers cannot make their own choices. Here is a well-mannered header file for count.cpp:

// count.h
#include <iostream>
int count(std::istream& is);

In an office context, surnames are necessary only if there are multiple people with the same first name. Keeping the standard definitions in std means that you can avoid problems with namespace collision. Say you have to use an old library that has defined its very own string type (writing your own string library was common before the standard library was developed) and called it string. In this case you would not inject std into the global namespace because then the two definitions of string would collide with each other. Using std::string makes it absolutely clear what kind of string you're working with. The same is true for simple, often-used names such as list, vector, and map. It is still tedious to write std::cout, but you can always use using declarations (as opposed to using directives), as in the following example. These actually make the name available in the global namespace:

using std::cout;
using std::endl;
using std::cerr;

Why can't you just change the headers for the library and avoid all the extra typing? You could then change each library's string to lib_string, for example. But this would not help, unless you actually had the libraries' source code (and with commercial libraries, you usually don't) because the function references in the object files still contain string, not lib_string.

You can of course add your own functions to std, but that would defeat the purpose of std, which is to collect together the standard library functions and structures. The std types all appear in lowercase, so it is helpful to use the naming convention of beginning types with an uppercase letter so that it is obvious what the types are.

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

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