Solution

I l @ ve RuBoard

graphics/bulb.gif

Migrating to Namespaces, Safely and Effectively

The situation described in this question is typical for projects that are just moving to compilers with added support for namespaces, or projects that are just moving away from legacy non-namespace headers. The main problem that needs a solution is that, now, the standard library is in namespace std and so unqualified uses of std:: names in the project code will fail to compile.

For example, code that used to work, such as the following:

 // Example 40-1: This used to work // #include <iostream.h>  // "legacy" pre-standard header int main() {   cout << "hello, world" << endl; } 

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

 // Example 40-2(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 40-2(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 40-2(c): Option C, write using-directives // #include <iostream> int main() {  using namespace std;  // or this can go at file scope   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? The rest of this Item 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 determine 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: Never write using-directives in header files.

The reason for Rule #1 is that using-directives causes 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 conflictsnot just in the header itself, but in every module that #include s the header. I find it helpful to think of a using-directive in a header file 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 the qualifier " namespace using-declarations." Of course, you should feel free to write class member using-declarations to bring in base class names as needed.)

This one might surprise you, because 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.

In my opinion the above "popular advice" doesn't go far enough. 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 #include d before it in any given module. This kind of unpredictability should never be permitted, for not only is it bad, but it can make your program illegal. Consider just this one possible case: If the header file has the inline code definition for a function f() (for example, say f() is a template, or is part of a template), and some code line within f() changes meaning depending on #include s, then the whole program is illegal because it violates the One Definition Rule (ODR). The ODR requires that the definition of any given entity, here f() , must be identical in every translation unit where it's used. Because it's not, we're banished to Undefined Behavior Land. Even when ODR violations aren't involved, though, unpredictability is still bad, and I'll illustrate this problem again later in Example 40-3(c).

Namespace Rule #3: In implementation files, never write a using-declaration or a using-directive before an #include directive.

All using-declarations and using-directives must come after all #include directives. Otherwise , there's a chance the using could change the semantics of the #include d header by introducing unexpected names.

Other than that, in your implementation files you can go ahead and use whatever combination of using-declarations and/or using-directives make sense, at file scope and/or function scope, without feeling guilty. Your local decision will be driven by what's convenient for you in that particular file, and won't have potential side effects for other implementation files. Using-declarations and using-directives exist for your convenience, not you for theirs. Do what makes sense (if you have no name collisions, why not just use all names?), as long as it affects only your own translation unit and not anybody else's headers or translation units.

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

This one really isn't as big a deal in practice, but I mention it for theoretical completeness and to keep the standardistes' safety patrol off my back. For backward compatibility with C, C++ still supports all the standard C header names (for example, 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 (for example, 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 .

So why do I say it's not really a big deal? First, because even though the " name . h " names are officially deprecated, you can bet your coffee money they will never really disappear. Speaking quite frankly, and just between us and these four walls, even if the committee did remove them at some future date, the standard library vendors would still ship the old headers forever as extensions. Why? Because many of you, their customers, have way too much production code out there that uses them. And, of course, the C++ standards committee is aware of this because it's inhabited in part by those same vendors . 'Nuff said. Second, you may have reason to prefer the " name . h " headers because some other standards, such as Posix, require adding new names to the " name . h " headers. If you're using such extensions, it's theoretically safer for you to use the " name . h " style, even though library vendors are likely to provide those same names in the " cname " headers, too, as a common extension for your convenience and for consistency, even though no standard officially requires it.

Long-Term Approaches: A Motivating Example

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

 // Example 40-3(a): Original namespace-free code // //--- file x.h --- // #include "y.h"  // defines Y #include <deque.h> #include <iosfwd.h> ostream& operator<<( ostream&, const Y& ); Y operator+( const Y&, int ); int f( const deque<int>& ); //--- file x.cpp --- // #include "x.h" #include "z.h"  // defines Z #include <ostream.h> ostream& operator<<( ostream& o, const Y& y ) {   // ... uses Z in the implementation ...   return o; } Y operator+( const Y& y, int i ) {   // ... uses another operator+() in the implementation...   return result; } int f( const deque<int>& d ) {   // ... } 

How can we best migrate the preceding 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 the 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 strategiesonly 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.

 // Example 40-3(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& ); Y operator+( const Y&, int i ); int f( const  std::  deque<int>& ); //--- file x.cpp --- // #include "x.h" #include "z.h"  // defines Z #include <ostream>  using std::deque;   // "using" appears AFTER all #includes   using std::ostream;   using std::operator+;   // or, "using namespace std;" if that suits you  ostream& operator<<( ostream& o, const Y& y ) {   // ... uses Z in the implementation ...   return o; } Y operator+( const Y& y, int i ) {   // ... uses another operator+() in the implementation...   return result; } 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 #include d. In particular, uses of x . h 's operator+() might end up being ambiguous depending on what other operator+() functions are visible, which depends on which standard headers happen to be included before or after the using declaration.

I sometimes encounter people who, even in their . cpp files, like to eschew " using " entirely and explicitly qualify every standard name. I don't recommend doing this, because it's a lot of extra work that normally doesn't deliver any real advantage.

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 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 using-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 40-3(c): Bad long-term solution // (or, Why to never write using-declarations // in headers, even within a namespace) // //--- 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& ); Y operator+( const Y&, int ); 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   {   using std::operator+;  ostream& operator<<( ostream& o, const Y& y )   {     // ... uses Z in the implementation ...     return o;   }   Y operator+( const Y& y, int i )   {     // ... uses another operator+() in the implementation...     return result;   }   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 changeeven when the using-declaration is inside a namespace, not at file scopedepending on what else a client module may happen to #include before it. For example, the statement using std::operator<<; can mean very different things, as can using std::operator+; , depending on what headers have been included so far (never mind that the answer will also vary from one standard library implementation to another). 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 #include d.

An Effective Short-Term Solution

The reason I've spent time explaining the desirable long-term solution, and discrediting some bad approaches, is that knowing what we want to achieve eventually 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 using-directives in the headers?" Well, adding using-declarations or using-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 Item already reject such an approach, and we've already seen what I hope you'll agree are compelling reasons not to go down that road. Given that we mustn't add using-declarations or using-directives in headers, our only alternative for headers is to add " std:: " in the right places.

You can do the bulk of the work quickly using automated text substitutionfor example using a tool to change " string " to " std::string " (and so forth for common names like vector and list ) in all " * . h* " files. Be careful that you don't accidentally substitute common names that also appear in other namespaces you're using, such as if you're using a third-party tool that has its own nonstandard string . Such names will have to be resolved manually.

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 #include s.

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. This doesn't violate the spirit of Rule #1 because this header is special. It's not a normal header that declares something for later use, but a mechanism for injecting code into implementation files at a particular well-controlled point.

Doing it this way has a small advantage over just pasting " using namespace std; " into all your implementation files. On many projects, it's common to have such a header, and so you may have something like myproject_last . h already. If so, you only have to write one line and you're done. If you don't already have such a header, you'll probably find having one to be convenient later on. In any case, whether you paste the line " using namespace std; " or the line " #include " myproject_last . h " " into every file represents the same total amount of work.

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 (or ever, really).

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

 // Example 40-3(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& );   Y operator+( const Y& y, int i ); 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; } Y operator+( const Y& y, int i ) {   // ... uses another operator+() in the implementation...   return result; } 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.

Conclusion: Migrating to the Long-Term Solution

Finally, at some happy and quite possibly imaginary 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 40-3(b). Simply follow these steps.

  1. In myproject_last . h , comment out the using-directive.

  2. Rebuild your project. See what breaks the compile. In each implementation file, add the correct using-declarations and/or using-directives, either at file scope (after all #include s) or within each function. Season to taste.

  3. Optionally, 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> . This can usually be done quickly using automated text substitution tools. It can be done even more quickly by skipping this step entirely.

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.

I l @ ve RuBoard


More Exceptional C++
More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions
ISBN: 020170434X
EAN: 2147483647
Year: 2001
Pages: 118
Authors: Herb Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net