TITLE: Migrating to Namespaces (Newsgroups: comp.lang.c++.moderated, 30 Apr 99) AUTHOR: Herb Sutter Subject: Guru of the Week #53: Solution > ------------------------------------------------------------------- > Guru of the Week problems and solutions are posted regularly on > news:comp.lang.c++.moderated. For past problems and solutions > see the GotW archive at www.peerdirect.com. (c) 1999 H.P.Sutter > News archives may keep copies of this article. > ------------------------------------------------------------------- > >_______________________________________________________ > >GotW #53: Migrating To Namespaces > >Difficulty: 4 / 10 >_______________________________________________________ > > >Congratulations to Bill Wade, the Guru of the Week! > > >>JG Question >>----------- >> >>1. What are using declarations and using directives, >> and how are they used? Give examples. > >Using Declarations >------------------ > >A using declaration creates a local synonym for a >specific name actually declared in another namespace. >For the purposes of overloading and name resolution, a >using declaration works just like any declaration. > > // Example 1a > // > namespace A > { > int f( int ); > int i; > } > > using A::f; > int f( int ); > > int main() > { > f( 1 ); // ambiguous: A::f() or ::f()? > > int i; > using A::i; // error, double declaration (just as > // writing "int i;" again would be) > } > >A using declaration only brings in names whose >declarations have already been seen. For example: > > // Example 1b > // > namespace A > { > class X {}; > int Y(); > int f( double ); > } > > using A::X; > using A::Y; // only function A::Y(), not class A::Y > using A::f; // only A::f(double), not A::f(int) > > namespace A > { > class Y {}; > int f( int ); > } > > int main() > { > X x; // OK, X is a synonym for A::X > Y y; // error, A::Y not visible because the > // using declaration preceded the > // actual declaration that's wanted > > f( 1 ); // oops: this uses a silent implicit > // conversion and calls A::f(double), > // NOT A::f(int), because only the > // first A::f() declaration had been > // seen at the point of the using > // declaration for the name A::f > } > > >Using Directives >---------------- > >A using directive allows all names in another namespace >to be used in the scope of the using directive. Unlike >a using declaration, a using directive brings in names >declared after the using directive. For example: > > // Example 1c > // > namespace A > { > class X {}; > int f( double ); > } > > using namespace A; > > namespace A > { > class Y {}; > int f( int ); > } > > int main() > { > X x; // OK, X is a synonym for A::X > Y y; // OK, Y is a synonym for A::Y > > f( 1 ); // OK, calls A::f(int) > } > > >>Bonus: What does MLOC stand for? > >MLOC stands for millions of lines of code. Typically, >the measurement counts only noncomment, nonblank lines. > > >Migrating To Namespaces, Safely and Effectively >----------------------------------------------- > >Background: The situation described in Question #2 is >pretty typical for projects whose compilers are >supporting namespaces for the first time. > >Note that in such a situation by definition there can't >already be name conflicts in the existing project >(including both custom code and libraries) because >namespaces didn't yet exist, so 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 now fail to compile. > >>Guru Question >>------------- >> >>2. You are working on a MLOC project with over 1,000 .h >> and .cpp files. The project team is just upgrading >> to the latest version of your compiler, which >> finally both supports namespaces and puts all of the >> standard library features into namespace std. >> Unfortunately, this conformance boon has the side >> effect of breaking your current build. > >First, let's go on a short tangent to analyze the best >long-term solution. Then we'll know better what to >look for in a good short-term solution that won't end >up being just thrown away and reworked when we get to >the long-term solution. > > >Characteristics of a Good Long-Term Solution >-------------------------------------------- > >> As always, >> there is never enough time allocated to do a >> detailed job; you would like to analyze every source >> file and write exactly the needed using-declarations >> in each one, but that's infeasible at this time. > >In short, a good long-term solution for namespace usage >should follow at least these rules: > >1. Using directives should be avoided entirely, > especially in header files.[*] > >[*] Unless of course the header is only #included by > one module, but this case is rare enough that I > didn't want to cause possible confusion by writing > "...in shared header files," lest someone think > this advice applies only to headers shared between > projects. It applies to all headers that are > #included by more than one module. > >The reason for Guideline #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, but in every module that #includes the >header. > >2. Namespace using declarations should never appear in > header files. > >The reason for Guideline #2 is less obvious. > >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 using directives, only less of it. > >Unfortunately, in my opinion the above "usual 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. >(I'll come back to this point again when I get to >Example 2c later on.) > >Think about what this means. To illustrate, consider >the following example module and two good long-term >approaches to migrating the module to namespaces: > > // Example 2a: Original namespace-free code > // > > //--- file x.h --- > // > #include "y.h" // declares Y > #include > #include > > ostream& operator<<( ostream&, const Y& ); > int f( const deque& ); > > //--- file x.cpp --- > // > #include "x.h" > #include "z.h" // declares Z > #include > > ostream& operator<<( ostream& o, const Y& y ) > { > // ... uses Z in the implementation ... > return o; > } > > int f( const deque& d ) > { > // ... > } > >How can we best migrate the above code to a compiler >that respects namespaces and puts 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? > > * * * > >There are several ways you might approach this. 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 x.h, >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 2b: Good long-term solution > // > > //--- file x.h --- > // > #include "y.h" // declares Y > #include > #include > > std::ostream& operator<<( std::ostream&, const Y& ); > int f( const std::deque& ); > > //--- file x.cpp --- > // > #include "x.h" > #include "z.h" // declares Z > #include > > 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& 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 #include orders. > >[An alternative good long-term solution would be to do >the above but eschew the using declarations entirely >and explicitly qualify each standard name, even inside >the .cpp file. I don't recommend doing that, because >it's a lot of extra work that doesn't deliver any real >advantage in a typical situation like this.] > > >A Not-So-Good Long-Term Solution >-------------------------------- > >In contrast, a bad long-term solution would be to put >all of the project's code into its own namespace (which >is not objectionable in itself, but isn't as useful as >you might think) and write the using declarations or >directives in the headers (which opens the door for >potential problems). The reason some people have >proposes this method is that it requires less typing >than some other namespace-migration methods: > > // Example 2c: Bad long-term solution (or, Why to > // avoid using declarations in > // headers, even not at file scope) > // > > //--- file x.h --- > // > #include "y.h" // declares MyProject::Y and adds > // using declarations/directives > // in namespace MyProject > #include > #include > > namespace MyProject > { > using std::deque; > using std::ostream; > // or, "using namespace std;" > > ostream& operator<<( ostream&, const Y& ); > int f( const deque& ); > } > > //--- file x.cpp --- > // > #include "x.h" > #include "z.h" // declares 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 > #include > > namespace MyProject > { > ostream& operator<<( ostream& o, const Y& y ) > { > // ... uses Z in the implementation ... > return o; > } > > int f( const deque& d ) > { > // ... > } > } > >Note the 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. >It's ALWAYS bad form to write any kind of code that can >silently change meaning depending on the order that >headers are #included. (It's also bad form to write >header code that requires the user to #include other >headers manually ahead of it; headers should always be >self-contained and work correctly even if #included >alone.) > > >An Effective Short-Term Solution >-------------------------------- > >The reason why I've spent time explaining desirable >long-term solutions is so that, when we pick a simpler >short-term solution, we pick one that won't compromise >a good long-term solution: > >> What is the most effective way to deal with this >> problem and safely migrate your code base to the new >> (and more standard) environment? Discuss >> alternatives, and prefer the quickest 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? > >Adding using declarations or directives in the headers >would be the least work, but it's work that would have >to be undone when we implement the correct long-term >solution. We've already seen enough reasons not to go >down that road. Given that we mustn't add using >declarations or directives in headers, our only >alternative is to add "std::" in the right places in >the headers. > >We can, however, get away with writing a using >directive in each implementation file, which >avoids/defers the tedious work of figuring out the >(possibly lengthy) correct set of using declarations >that will eventually go into each implementation file. >Note that this doesn't compromise -- or change the >meaning -- of any other code in any other modules, >including the headers we #include, as long as we're >careful to write the using directive after all of the >#include statements. For convenience, and for a good >reason that will become clearer in a moment, I'll put >the using directive into a separate header to be >#included after all others. > >Finally, we can defer the work of moving to the new > C header style too, which also defers all of >the associated namespace issues. > >Here's the result: > > // Example 2d: Good short-term solution is to: > // - in headers, write std:: as needed > // - in .cpp files, #include > // "myproject_last.h" after all > // other headers > // > > //--- file x.h --- > // > #include "y.h" // declares Y > #include > #include > > std::ostream& operator<<( std::ostream&, const Y& ); > int f( const std::deque& ); > > //--- file x.cpp --- > // > #include "x.h" > #include "z.h" // declares Z > #include > #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& 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. > > >Migrating To the Long-Term Solution >----------------------------------- > >In the future, this allows a simple migration to the >full long-term solution. Simply follow these steps: > >- in each header: change lines that #include C headers > to the new style; e.g., change "#include > " to "#include " > >- in myproject_last.h: comment out the using directive > >- rebuild; in each implementation file, see what breaks > and 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. > (Newsgroups: comp.lang.c++.moderated, 1 May 99) SUTTER: On 30 Apr 1999 13:35:04 -0400, Herb Sutter wrote: >MLOC stands for millions of lines of code. Typically, >the measurement counts only noncomment, nonblank lines. NARAN: Siemel B. Naran (sbnaran@uiuc.edu) Comment lines should be counted too because they are very important. So important that perhaps the number of lines should be defined as 2*comment_lines + code_lines Well, I'm sort of kidding here. But comments are still important. Although I've seen lots of useless comments like // initialize the grid InitGrid(...); There's also another point I forgot to mention, though it's not directly related to your GotW. Use incremental testing. If you have files MyClass.h and MyClass.cc, write a file MyClass.test.cc that tests the functionality of MyClass.h. Now when you upgrade file MyClass.h and MyClass.cc for whatever reason, you have just to recompile the test driver to see that everything compiles and works as expected. I learned his idea from the book by John Lakos, and it is a very good idea.