Migrating to Namespaces

If you aren't familiar with C++ namespaces, you should be because most compilers now support them.


October 01, 2000
URL:http://www.drdobbs.com/cpp/migrating-to-namespaces/184404271

Oct00: Migrating to Namespaces

Herb is chief technology officer of PeerDirect Inc. He also serves as secretary of the ISO/ANSI C++ Standards committee, and chair of ANSI SQL/Replication. He is the author of the books Exceptional C++ and the forthcoming Peer Distributed Databases (Addison-Wesley, 2000). Herb can be contacted at [email protected].


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 -- the addition of namespaces. More specifically, all of the C++ Standard Library now lives in namespace std.

If this hasn't troubled you yet, then it probably will soon, because the newest releases of most C++ compilers conform to this change. As you start upgrading to the latest release of your compiler, your code will be forced to adapt.

For instance, Example 1(a) used to work, but now requires that you either specifically say which names are in std, see Example 1(b); write using declarations to bring the necessary std names into scope, see Example 1(c); write a "using directive" to drag all the std names into scope wholesale, see Example 1(d); or some combination of the aforementioned. 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, deferring unnecessary (for now) migration work without increasing the overall migration workload later? I'll address these questions in this article.

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 you decide what to aim for when you craft your best short-term migration strategy.

In short, a good long-term solution for namespace usage should follow 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 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. Rule #2 goes much further than most popular advice. Most programmers 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, the popular advice does not 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 in Example 2.

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 (stdio.h, for instance) 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 (that is, 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

To illustrate these rules, consider Example 2 and two long-term approaches for migrating the module to namespaces. How can you best migrate Example 2(a) to a compiler that respects namespaces and a library that puts the standard names in namespace std? 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, because those names are likely to be widely used in the source file; see Example 2(b). 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 long-term solution is to do Example 2(b), but eschew the using declarations entirely and explicitly qualify every single standard name, even inside the .cpp file. I don't recommend doing this, however, simply 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; see Example 2(c). 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 I've spent time explaining the desirable long-term solution, and discrediting some bad approaches, is because knowing what you want to eventually achieve will help you pick a simpler short-term solution that won't compromise your long-term solution. So now I'll 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 your 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? 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 already reject such an approach, and you've already seen compelling reasons not to go down that road. Given that you mustn't add using declarations or directives in headers, then, your 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: You 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 you'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 your table for the evening, but you're chaining them to their seats. The using directive is best put into a separate header that is then #included in each implementation file after all other headers, both for safety (for example, 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. Finally, what about the new <cheader> header style?

Fortunately, that's optional, so it doesn't need to be done during the initial migration pass, which also defers all of the associated namespace issues.

Example 2(d) is the result of applying the two-step migration strategy to Example 2(a). 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 a full long-term solution would. In fact, this approach represents the minimum amount of work you can get away with that will make your code work on a namespace-aware compiler but that won't make you 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; for example, 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.

DDJ

Oct00: Migrating to Namespaces


(a)
#include <iostream.h>
int main()
{
  cout << "hello, world" << endl;
}

(b)
#include <iostream>
int main()
{
  std::cout << "hello, world" << std::endl;
}

(c)
#include <iostream>
using std::cout;
using std::endl;
int main()
{
  cout << "hello, world" << endl;
}

(d)
#include <iostream>
using namespace std;
int main()
{
  cout << "hello, world" << endl;
}

Example 1: (a) Code that used to work; (b) Option 1, specify everything; (c) Option 2, write using declarations; (d) Option 3, write using directives.

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.