Migrating to Namespaces

Home Blog Talks Books & Articles Training & Consulting

Prev
Up
Next

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original article substantially as first published. See the book More Exceptional C++ (Addison-Wesley, 2002) for the most current version of this article. The versions in the book have been revised and expanded since their initial appearance in print. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard.

Migrating to Namespaces

This article appeared in Dr. Dobb's Journal, 25(10), October 2000.

 

Although C++ changed greatly during standardization, few of the changes the committee made are likely to break existing code. There is one change, however, that will cause nearly all earlier C++ code to fail to compile, to wit: the addition of namespaces, specifically the fact that all of the C++ standard library now lives in namespace std.

If you are a C++ programmer and this hasn't troubled you yet, then it probably will soon, because we're now in the situation where the newest releases of most compilers have conformed to this change. As you start upgrading to the latest release of your compiler, your code will be forced to adapt.

For example, code that used to work:

// Example 1: This used to work
//
#include <iostream.h>

int main()
{
  cout << "hello, world" << endl;
}

now requires that you either specifically say which names are in std:

// Example 1(a): Option A, specify everything
//
#include <iostream>

int main()
{
  std::cout << "hello, world" << std::endl;
}

or write "using declarations" to bring the necessary std names into scope:

// Example 1(b): Option B, write using declarations
//
#include <iostream>
using std::cout;
using std::endl;

int main()
{
  cout << "hello, world" << endl;
}

or write a "using directive" to simply drag all the std names into scope wholesale:

// Example 1(c): Option C, write using directives
//
#include <iostream>
using namespace std;

int main()
{
  cout << "hello, world" << endl;
}

or some combination of the above.

So, what's the right way to use namespaces in the long run? And what's the best way to move a large body of existing code to the new rules quickly today, deferring unnecessary (for now) migration work to the future without increasing the overall migration workload later? This article answers these questions.

Guidelines For a Good Long-Term Solution

First, what's the right way to use namespaces in the long run? It's important to decide this first, because knowing the long-term answer will help us decide what to aim for when we craft our best short-term migration strategy.

In short, a good long-term solution for namespace usage should follow at least these rules:

Namespace Rule #1: Avoid using directives entirely, especially in header files.

The reason for Rule #1 is that using directives cause wanton namespace pollution by bringing in potentially huge numbers of names, many (usually the vast majority) of which are unnecessary. The presence of the unnecessary names greatly increases the possibility of unintended name conflicts--not just in the header itself, but in every module that #includes the header. I find it helpful to think of a using directive as a marauding army of crazed barbarians that sows indiscriminate destruction wherever it passes--something that by its mere presence can cause "unintended conflicts," even when you think you're allied with it.

Namespace Rule #2: Never write namespace using declarations in header files.

Note that Rule #2 goes much further than most popular advice. Most authors recommend that using declarations never appear at file scope in shared header files. That's good advice, as far as it goes, because at file scope a using declaration causes the same kind of namespace pollution as a using directive, only less of it.

Unfortunately, in my opinion the above "popular advice" doesn't go far enough. Here is why you should never write using declarations in header files at all, not even in a namespace scope: The meaning of the using declaration may change depending on what other headers happen to be #included before it in any given module. This kind of unpredictability is bad, and should never be permitted. I'll illustrate this problem later in Example 2(c).

Namespace Rule #3: Use C headers with the new style "#include <cheader>"
instead of the old style "#
include <header.h>".

For backward compatibility with C, C++ still supports all of the standard C header names (e.g., stdio.h), and when you #include those original versions the associated C library functions are visible in the global namespace as before--but in the same breath C++ also says that the old header names are deprecated, which puts the world on notice that they may be removed in a future version of the C++ standard. Thus Standard C++ strongly encourages programmers to prefer using the new versions of the C headers that start with "c" and drop the ".h" extension (e.g., cstdio); when you #include the C headers using the new names, you get the same C library functions, but now they live in namespace std.

Long-Term Approaches: A Motivating Example

To illustrate the above rules, consider the following example module and two good long-term approaches for migrating the module to namespaces:

// Example 2(a): Original namespace-free code
//

//--- file x.h ---
//
#include "y.h" // defines Y
#include <deque>
#include <iosfwd>

ostream& operator<<( ostream&, const Y& );
int f( const deque<int>& );

//--- file x.cpp ---
//
#include "x.h"
#include "z.h" // defines Z
#include <ostream>

ostream& operator<<( ostream& o, const Y& y )
{
  // ... uses Z in the implementation ...
  return o;
}

int f( const deque<int>& d )
{
  // ...
}

How can we best migrate the above code to a compiler that respects namespaces and a library that puts the standard names in namespace std? Before reading on, please stop for a moment and think about what alternatives you might consider. Which ones are good? Which ones aren't? Why?

Have you made a decision? All right, then. There are several ways you might approach this, and I'll describe two common strategies--only one of which is good form.

A Good Long-Term Solution

A good long-term solution is to explicitly qualify every standard name wherever it is mentioned in a header (.h) file, and to write just the using declarations that are needed inside each source (.cpp) file for convenience, since those names are likely to be widely used in the source file:

// Example 2(b): A good long-term solution
//

//--- file x.h ---
//
#include "y.h" // defines Y
#include <deque>
#include <iosfwd>

std::ostream& operator<<( std::ostream&, const Y& );
int f( const std::deque<int>& );

//--- file x.cpp ---
//
#include "x.h"
#include "z.h" // defines Z
#include <ostream>
using std::deque;   // using declarations appear
using std::ostream; // AFTER all #includes

ostream& operator<<( ostream& o, const Y& y )
{
  // ... uses Z in the implementation ...
  return o;
}

int f( const deque<int>& d )
{
  // ...
}

Note that the using declarations inside x.cpp had better come after all #include directives, otherwise they may introduce name conflicts into other header files depending on the order in which they're #included.

An alternative good long-term solution would be to do the above but eschew the using declarations entirely and explicitly qualify every single standard name, even inside the .cpp file. I don't recommend doing this, because it's a lot of extra work that doesn't deliver any real advantage in a typical situation.

A Not-So-Good Long-Term Solution

In contrast, one often-proposed solution is actually dangerous. This bad long-term 'solution' proposes to put all of the project's code into its own namespace (which is not objectionable in itself, but isn't as useful as one might think) and write the using declarations or directives in the headers (which unintentionally opens a gaping door for potential problems). The reason some people have proposed this method is that it requires less typing than some other namespace-migration methods:

// Example 2(c): Bad long-term solution
// (or, Why to never write using declarations
// in headers, even at namespace scope)
//

//--- file x.h ---
//
#include "y.h" // defines MyProject::Y and adds
               // using declarations/directives
               // in namespace MyProject
#include <deque>
#include <iosfwd>

namespace MyProject
{
  using std::deque;
  using std::ostream;
  // or, "using namespace std;"

  ostream& operator<<( ostream&, const Y& );
  int f( const deque<int>& );
}

//--- file x.cpp ---
//
#include "x.h"
#include "z.h" // defines MyProject::Z and adds
               // using declarations/directives
               // in namespace MyProject
  // error: potential future name ambiguities in
  // z.h's declarations, depending on what
  // using declarations exist in headers
  // that happen to be #included before z.h
  // in any given module (in this case,
  // x.h or y.h may cause potential changes
  // in meaning)

#include <ostream>

namespace MyProject
{
  ostream& operator<<( ostream& o, const Y& y )
  {
    // ... uses Z in the implementation ...
    return o;
  }

  int f( const deque<int>& d )
  {
    // ...
  }
}

Note the highlighted error: The reason, as stated, is that the meaning of a using declaration in a header can change--even when the using declaration is inside a namespace, and not at file scope--depending on what else a client module may happen to #include before it. Clearly, it's always bad form to write any kind of code that can silently change meaning depending on the order in which headers are #included.

An Effective Short-Term Solution

The reason why I've spent time explaining the desirable long-term solution, and discrediting some bad approaches, is because knowing what we want to eventually achieve will help us to pick a simpler short-term solution that won't compromise our long-term solution. So now we're ready to tackle the short-term question: What is the most effective way to migrate your existing code base to deal with namespace std?

Speaking pragmatically, the "upgrade to the new compiler version" task is just one of a list of things to be done in our product's release cycle. Upgrading the compiler and migrating to namespaces probably isn't the most urgent item on the task list and you have lots of other things to do and product features to add; more likely than not you're under project deadline pressure to boot, so you should prefer the quickest namespace migration approach that gets the job done without compromising future safety and usability. How can you best defer unnecessary (for now) migration work to the future without increasing the overall migration workload later? First, consider migrating the header files:

Migration Step #1: In every header file, add "std::" qualifiers wherever needed.

"Is adding std:: everywhere really necessary?" you might ask. "Couldn't you just add using declarations or directives in the headers?" Well, adding using declarations or directives would indeed be the least work, but it's work that would have to be undone when you implement the correct long-term solution; Namespace Rules #1 and #2 presented earlier in this article already reject such an approach, and we've already seen compelling reasons not to go down that road. Given that we mustn't add using declarations or directives in headers, then, our only alternative for headers is to add "std::" in the right places.

Migration Step #2: Create a new header file called myproject_last.h that contains the directive using namespace std;. In every implementation file, #include myproject_last.h after all other #includes.

Things are a little better with implementation files: We can get away with simply writing a using directive in each implementation file, as long as the using directive appears after all #include statements. Writing a using directive avoids/defers the tedious work of figuring out the (possibly lengthy) correct set of using declarations that will eventually go into each implementation file; yes, that's right, as a temporary expedient you are deliberately choosing to invite an army of crazed barbarians to dinner. But never fear: as long as we're careful to write the using directive after all #includes, this doesn't compromise--or change the meaning of-- any other code in any other modules; the barbarians may be at our table for the evening, but we're chaining them to their seats. Note that the using directive is best put into a separate header which is then #included in each implementation file after all other headers, both for safety (i.e., to ensure you are sending only a single, well-controlled invitation to the army of crazed barbarians), and for another good reason that will become clearer in a moment when we consider what's left to migrate to the full long-term solution.

Finally, what about the new <cheader> header style? Fortunately, that's optional, and so it doesn't need to be done during the initial migration pass, which also defers all of the associated namespace issues.

Here's the result of applying our two-step migration strategy to Example 2(a):

// Example 2(d): Good short-term solution,
// applying our two-step migration
//

//--- file x.h ---
//
#include "y.h" // defines Y
#include <deque>
#include <iosfwd>

std::ostream& operator<<( std::ostream&, const Y& );
int f( const std::deque<int>& );

//--- file x.cpp ---
//
#include "x.h"
#include "z.h" // defines Z
#include <ostream>
#include "myproject_last.h" // AFTER all other #includes

ostream& operator<<( ostream& o, const Y& y )
{
  // ... uses Z in the implementation ...
  return o;
}

int f( const deque<int>& d )
{
  // ...
}

//--- common file myproject_last.h ---
//
using namespace std;

This does not compromise the long-term solution in that it doesn't do anything that will need to be "undone" for the long-term solution. At the same time, it's simpler and requires fewer code changes than the full long-term solution would. In fact, this approach represents the minimum amount of work we can get away with that will make our code work on a namespace-aware compiler but that won't make us have to go back and undo any of the work later.

Migrating To the Long-Term Solution

Finally, at some happy point in the future when you are momentarily free of pressing project deadlines, you can perform a simple migration to the full long-term solution illustrated in Example 2(b). Simply follow these steps:

1.       In each header or implementation file: Change lines that #include C headers to the new <cheader> style; e.g., change "#include <stdio.h>" to "#include <cstdio>".

2.       In myproject_last.h: Comment out the using directive.

3.       Rebuild your project. See what breaks the compile. In each implementation file, add the correct using declarations (after all #includes).

If you follow this advice, then even with looming project deadlines you'll be able to quickly--and effectively--migrate to a namespace-aware compiler and library, all without compromising your long-term solution.

Copyright © 2009 Herb Sutter