B. Stroustrup - The C++ Programming Language (794319), страница 89
Текст из файла (страница 89)
We can always access names from ‘‘our own’’ namespaceexactly as we did before we introduced namespaces. However, for names in other namespaces, wehave to choose among explicit qualification, using-declarations, and using-directives.Parser::prim() provides a good test case for the use of namespaces in an implementation becauseit uses each of the other namespaces (except Driver). If we use explicit qualification, we get:double Parser::prim(bool get){if (get) Lexer::ts.get();// handle primariesswitch (Lexer::ts.current().kind) {case Lexer::Kind::number:// floating-point constant{double v = Lexer::ts.current().number_value;Lexer::ts.get();return v;}case Lexer::Kind::name:{double& v = Table::table[Lexer::ts.current().string_value];if (Lexer::ts.get().kind == Lexer::Kind::assign) v = expr(true); // ’=’ seen: assignmentreturn v;}case Lexer::Kind::minus:// unar y minusreturn −prim(true);case Lexer::Kind::lp:{double e = expr(true);if (Lexer::ts.current().kind != Lexer::Kind::rp) return Error::error(" ')' expected");Lexer::ts.get();// eat ’)’return e;}default:return Error::error("primary expected");}}I count 14 occurrences of Lexer::, and (despite theories to the contrary) I don’t think the moreexplicit use of modularity has improved readability.
I didn’t use Parser:: because that would beredundant within namespace Parser.If we use using-declarations, we get:using Lexer::ts;using Lexer::Kind;using Error::error;using Table::table;// saves eight occurrences of ‘‘Lexer::’’// saves six occurrences of ‘‘Lexer::’’// saves two occurrences of ‘‘Error ::’’// saves one occurrence of ‘‘Table::’’Section 14.3.2double prim(bool get){if (get) ts.get();Implementations403// handle primariesswitch (ts.current().kind) {case Kind::number:// floating-point constant{double v = ts.current().number_value;ts.get();return v;}case Kind::name:{double& v = table[ts.current().string_value];if (ts.get().kind == Kind::assign) v = expr(true);// ’=’ seen: assignmentreturn v;}case Kind::minus:// unar y minusreturn −prim(true);case Kind::lp:{double e = expr(true);if (ts.current().kind != Kind::rp) return error("')' expected");ts.get();// eat ’)’return e;}default:return error("primary expected");}}My guess is that the using-declarations for Lexer:: were worth it, but that the value of the others wasmarginal.If we use using-directives, we get:using namespace Lexer;using namespace Error;using namespace Table;double prim(bool get){// as before}// saves four teen occurrences of ‘‘Lexer::’’// saves two occurrences of ‘‘Error ::’’// saves one occurrence of ‘‘Table::’’// handle primariesThe using-declarations for Error and Table don’t buy much notationally, and it can be argued thatthey obscure the origins of the formerly qualified names.So, the tradeoff among explicit qualification, using-declarations, and using-directives must bemade on a case-by-case basis.
The rules of thumb are:[1] If some qualification is really common for several names, use a using-directive for thatnamespace.[2] If some qualification is common for a particular name from a namespace, use a using-declaration for that name.404[3][4]NamespacesChapter 14If a qualification for a name is uncommon, use explicit qualification to make it clear fromwhere the name comes.Don’t use explicit qualification for names in the same namespace as the user.14.3.3 Interfaces and ImplementationsIt should be clear that the namespace definition we used for Parser is not the ideal interface forParser to present to its users. Instead, that Parser declares the set of declarations that is needed towrite the individual parser functions conveniently.
The Parser’s interface to its users should be farsimpler:namespace Parser { // user interfacedouble expr(bool);}We see the namespace Parser used to provide two things:[1] The common environment for the functions implementing the parser[2] The external interface offered by the parser to its usersThus, the driver code, main(), should see only the user interface.The functions implementing the parser should see whichever interface we decided on as the bestfor expressing those functions’ shared environment. That is:namespace Parser {double prim(bool);double term(bool);double expr(bool);// implementer interfaceusing namespace Lexer;using Error::error;using Table::table;// use all facilities offered by lexer}or graphically:Parser(user interface)ParserDrivercode(implementer interface)ParsercodeThe arrows represent ‘‘relies on the interface provided by’’ relations.We could give the user’s interface and the implementer’s interface different names, but (becausenamespaces are open; §14.2.5) we don’t have to.
The lack of separate names need not lead to confusion because the physical layout of the program (see §15.3.2) naturally provides separate (file)names. Had we decided to use a separate implementation namespace, the design would not havelooked different to users:Section 14.3.3Interfaces and Implementations405namespace Parser { // user interfacedouble expr(bool);}namespace Parser_impl {using namespace Parser;// implementer interfacedouble prim(bool);double term(bool);double expr(bool);using namespace Lexer; // use all facilities offered by Lexerusing Error::error;using Table::table;}or graphically:Parser(user interface)Parser_implDrivercode(implementer interface)ParsercodeFor larger programs, I lean toward introducing _impl interfaces.The interface offered to implementers is larger than the interface offered to users. Had thisinterface been for a realistically sized module in a real system, it would change more often than theinterface seen by users.
It is important that the users of a module (in this case, Driver using Parser)be insulated from such changes.14.4 Composition Using NamespacesIn larger programs, we tend to use many namespaces. This section examines technical aspects ofcomposing code out of namespaces.14.4.1 Convenience vs. SafetyA using-declaration adds a name to a local scope. A using-directive does not; it simply rendersnames accessible in the scope in which they were declared.
For example:namespace X {int i, j, k;}406NamespacesChapter 14int k;void f1(){int i = 0;using namespace X;i++;j++;k++;::k++;X::k++;}void f2(){int i = 0;using X::i;using X::j;using X::k;i++;j++;k++;// make names from X accessible// local i// X::j// error : X’s k or the global k?// the global k// X’s k// error: i declared twice in f2()// hides global k// X::j// X::k}A locally declared name (declared either by an ordinary declaration or by a using-declaration) hidesnonlocal declarations of the same name, and any illegal overloading of the name is detected at thepoint of declaration.Note the ambiguity error for k++ in f1(). Global names are not given preference over namesfrom namespaces made accessible in the global scope.
This provides significant protection againstaccidental name clashes, and – importantly – ensures that there are no advantages to be gained frompolluting the global namespace.When libraries declaring many names are made accessible through using-directives, it is a significant advantage that clashes of unused names are not considered errors.14.4.2 Namespace AliasesIf users give their namespaces short names, the names of different namespaces will clash:namespace A {// shor t name, will clash (eventually)// ...}A::String s1 = "Grieg";A::String s2 = "Nielsen";However, long namespace names can be impractical in real code:Section 14.4.2namespace American_Telephone_and_Telegraph {// ...}Namespace Aliases407// too longAmerican_Telephone_and_Telegraph::String s3 = "Grieg";American_Telephone_and_Telegraph::String s4 = "Nielsen";This dilemma can be resolved by providing a short alias for a longer namespace name:// use namespace alias to shorten names:namespace ATT = American_Telephone_and_Telegraph;ATT::String s3 = "Grieg";ATT::String s4 = "Nielsen";Namespace aliases also allow a user to refer to ‘‘the library’’ and have a single declaration definingwhat library that really is.
For example:namespace Lib = Foundation_library_v2r11;// ...Lib::set s;Lib::String s5 = "Sibelius";This can immensely simplify the task of replacing one version of a library with another. By usingLib rather than Foundation_library_v2r11 directly, you can update to version ‘‘v3r02’’ by changingthe initialization of the alias Lib and recompiling. The recompile will catch source-level incompatibilities.
On the other hand, overuse of aliases (of any kind) can lead to confusion.14.4.3 Namespace CompositionOften, we want to compose an interface out of existing interfaces. For example:namespace His_string {class String { /* ... */ };String operator+(const String&, const String&);String operator+(const String&, const char∗);void fill(char);// ...}namespace Her_vector {template<class T>class Vector { /* ... */ };// ...}408NamespacesChapter 14namespace My_lib {using namespace His_string;using namespace Her_vector;void my_fct(String&);}Given this, we can now write the program in terms of My_lib:void f(){My_lib::String s = "Byron";// ...}// finds My_lib::His_string::Stringusing namespace My_lib;void g(Vector<String>& vs){// ...my_fct(vs[5]);// ...}If an explicitly qualified name (such as My_lib::String) isn’t declared in the namespace mentioned,the compiler looks in namespaces mentioned in using-directives (such as His_string).Only if we need to define something do we need to know the real namespace of an entity:void My_lib::fill(char c){// ...}// error : no fill() declared in My_libvoid His_string::fill(char c){// ...}// OK: fill() declared in His_stringvoid My_lib::my_fct(String& v)// OK: String is My_lib::String, meaning His_string::String{// ...}Ideally, a namespace should[1] express a logically coherent set of features,[2] not give users access to unrelated features, and[3] not impose a significant notational burden on users.Together with the #include mechanism (§15.2.2), the composition techniques presented here and inthe following subsections provide strong support for this.Section 14.4.4Composition and Selection40914.4.4 Composition and SelectionCombining composition (by using-directives) with selection (by using-declarations) yields the flexibility needed for most real-world examples.
With these mechanisms, we can provide access to avariety of facilities in such a way that we resolve name clashes and ambiguities arising from theircomposition. For example:namespace His_lib {class String { /* ... */ };template<class T>class Vector { /* ... */ };// ...}namespace Her_lib {template<class T>class Vector { /* ...
*/ };class String { /* ... */ };// ...}namespace My_lib {using namespace His_lib;using namespace Her_lib;// everything from His_lib// everything from Her_libusing His_lib::String;using Her_lib::Vector;// resolve potential clash in favor of His_lib// resolve potential clash in favor of Her_libtemplate<class T>class List { /* ... */ };// ...// additional stuff}When looking into a namespace, names explicitly declared there (including names declared byusing-declarations) take priority over names made accessible in another scope by a using-directive(see also §14.4.1). Consequently, a user of My_lib will see the name clashes for String and Vectorresolved in favor of His_lib::String and Her_lib::Vector. Also, My_lib::List will be used by defaultindependently of whether His_lib or Her_lib is providing a List.Usually, I prefer to leave a name unchanged when including it into a new namespace.