B. Stroustrup - The C++ Programming Language (794319), страница 90
Текст из файла (страница 90)
Then, Idon’t have to remember two different names for the same entity. However, sometimes a new nameis needed or simply nice to have. For example:namespace Lib2 {using namespace His_lib;using namespace Her_lib;using His_lib::String;using Her_lib::Vector;// everything from His_lib// everything from Her_lib// resolve potential clash in favor of His_lib// resolve potential clash in favor of Her_lib410NamespacesChapter 14using Her_string = Her_lib::String;template<class T>using His_vec = His_lib::Vector<T>;template<class T>class List { /* ... */ };// ...// rename// rename// additional stuff}There is no general language mechanism for renaming, but for types and templates, we can introduce aliases with using (§3.4.5, §6.5).14.4.5 Namespaces and OverloadingFunction overloading (§12.3) works across namespaces.
This is essential to allow us to migrateexisting libraries to use namespaces with minimal source code changes. For example:// old A.h:void f(int);// ...// old B.h:void f(char);// ...// old user.c:#include "A.h"#include "B.h"void g(){f('a'); // calls the f() from B.h}This program can be upgraded to a version using namespaces without changing the actual code:// new A.h:namespace A {void f(int);// ...}// new B.h:namespace B {void f(char);// ...}Section 14.4.5Namespaces and Overloading411// new user.c:#include "A.h"#include "B.h"using namespace A;using namespace B;void g(){f('a');}// calls the f() from B.hHad we wanted to keep user.c completely unchanged, we would have placed the using-directives inthe header files. However, it is usually best to avoid using-directives in header files, becauseputting them there greatly increases the chances of name clashes.This overloading rule also provides a mechanism for extending libraries.
For example, peopleoften wonder why they have to explicitly mention a sequence to manipulate a container using astandard-library algorithm. For example:sort(v.begin(),v.end());Why not write:sort(v);The reason is the need for generality (§32.2), but manipulating a container is by far the most common case. We can accommodate that case like this:#include<algorithm>namespace Estd {using namespace std;template<class C>void sort(C& c) { std::sort(c.begin(),c.end()); }template<class C, class P>void sort(C& c, P p) { std::sort(c.begin(),c.end(),p); }}Estd (my ‘‘extended std’’) provides the frequently wanted container versions of sort().
Those are ofcourse implemented using std::sort() from <algorithm>. We can use it like this:using namespace Estd;template<class T>void print(const vector<T>& v){for (auto& x : v)cout << v << ' ';cout << '\n';}412NamespacesChapter 14void f(){std::vector<int> v {7, 3, 9, 4, 0, 1};sort(v);print(v);sort(v,[](int x, int y) { return x>y; });print(v);sort(v.begin(),v.end());print(v);sort(v.begin(),v.end(),[](int x, int y) { return x>y; });print(v);}The namespace lookup rules and the overloading rules for templates ensure that we find and invokethe correct variants of sort() and get the expected output:013479974310013479974310If we removed the using namespace std; from Estd, this example would still work because std’ssort()s would be found by argument-dependent lookup (§14.2.4).
However, we would then not findthe standard sort()s for our own containers defined outside std.14.4.6 VersioningThe toughest test for many kinds of interfaces is to cope with a sequence of new releases (versions).Consider a widely used interface, say, an ISO C++ standard header. After some time, a new version is defined, say, the C++11 version of the C++98 header. Functions may have been added,classes renamed, proprietary extensions (that should never have been there) removed, typeschanged, templates modified. To make life ‘‘interesting’’ for the implementer, hundreds of millionsof lines of code are ‘‘out there’’ using the old header, and the implementer of the new version cannot ever see or modify them. Needless to say, breaking such code will cause howls of outrage, aswill the absence of a new and better version.
The namespace facilities described so far can be usedto handle this problem with very minor exceptions, but when large amounts of code are involved,‘‘very minor’’ still means a lot of code. Consequently, there is a way of selecting between two versions that simply and obviously guarantees that a user sees exactly one particular version. This iscalled an inline namespace:namespace Popular {inline namespace V3_2 { // V3_2 provides the default meaning of Populardouble f(double);int f(int);template<class T>class C { /* ...
*/ };}Section 14.4.6Versioning413namespace V3_0 {// ...}namespace V2_4_2 {double f(double);template<class T>class C { /* ... */ };}}Here, Popular contains three subnamespaces, each defining a version. The inline specifies that V3_2is the default meaning of Popular. So we can write:using namespace Popular;void f(){f(1);V3_0::f(1);V2_4_2::f(1);}// Popular ::V3_2::f(int)// Popular ::V3_0::f(double)// Popular ::V2_4_2::f(double)template<class T>Popular::C<T∗> { /* ... */ };This inline namespace solution is intrusive; that is, to change which version (subnamespace) is thedefault requires modification of the header source code. Also, naively using this way of handlingversioning would involve a lot of replication (of common code in the different versions).
However,that replication can be minimized using #include tricks. For example:// file V3_common:// ... lots of declarations ...// file V3_2:namespace V3_2 { // V3_2 provides the default meaning of Populardouble f(double);int f(int);template<class T>class C { /* ... */ };#include "V3_common"}// file V3_0.h:namespace V3_0 {#include "V3_common"}414NamespacesChapter 14// file Popular.h:namespace Popular {inline#include "V3_2.h"#include "V3_0.h"#include "V2_4_2.h"}I do not recommend such intricate use of header files unless it is really necessary.
The exampleabove repeatedly violates the rules against including into a nonlocal scope and against having asyntactic construct span file boundaries (the use of inline); see §15.2.2. Sadly, I have seen worse.In most cases, we can achieve versioning by less intrusive means. The only example I can thinkof that is completely impossible to do by other means is the specialization of a template explicitlyusing the namespace name (e.g., Popular::C<T∗>). However, in many important cases ‘‘in mostcases’’ isn’t good enough. Also, a solution based on a combination of other techniques is less obviously completely right.14.4.7 Nested NamespacesOne obvious use of namespaces is to wrap a complete set of declarations and definitions in a separate namespace:namespace X {// ...
all my declarations ...}The list of declarations will, in general, contain namespaces. Thus, nested namespaces are allowed.This is allowed for practical reasons, as well as for the simple reason that constructs ought to nestunless there is a strong reason for them not to. For example:void h();namespace X {void g();// ...namespace Y {void f();void ff();// ...}}The usual scope and qualification rules apply:void X::Y::ff(){f(); g(); h();}Section 14.4.7void X::g(){f();Y::f();}void h(){f();Y::f();X::f();X::Y::f();}Nested Namespaces415// error: no f() in X// OK// error: no global f()// error: no global Y// error: no f() in X// OKFor examples of nested namespaces in the standard library, see(§35.5.3).chrono(§35.2) andrel_ops14.4.8 Unnamed NamespacesIt is sometimes useful to wrap a set of declarations in a namespace simply to protect against thepossibility of name clashes. That is, the aim is to preserve locality of code rather than to present aninterface to users.
For example:#include "header.h"namespace Mine {int a;void f() { /* ... */ }int g() { /* ... */ }}Since we don’t want the name Mine to be known outside a local context, it simply becomes a botherto invent a redundant global name that might accidentally clash with someone else’s names. In thatcase, we can simply leave the namespace without a name:#include "header.h"namespace {int a;void f() { /* ...
*/ }int g() { /* ... */ }}Clearly, there has to be some way of accessing members of an unnamed namespace from outsidethe unnamed namespace. Consequently, an unnamed namespace has an implied using-directive.The previous declaration is equivalent tonamespace $$$ {int a;void f() { /* ... */ }int g() { /* ... */ }}using namespace $$$;416NamespacesChapter 14where $$$ is some name unique to the scope in which the namespace is defined. In particular,unnamed namespaces in different translation units are different.
As desired, there is no way ofnaming a member of an unnamed namespace from another translation unit.14.4.9 C HeadersConsider the canonical first C program:#include <stdio.h>int main(){printf("Hello, world!\n");}Breaking this program wouldn’t be a good idea. Making standard libraries special cases isn’t agood idea either. Consequently, the language rules for namespaces are designed to make it relatively easy to take a program written without namespaces and turn it into a more explicitly structured one using namespaces. In fact, the calculator program (§10.2) is an example of this.One way to provide the standard C I/O facilities in a namespace would be to place the declarations from the C header stdio.h in a namespace std:// cstdio:namespace std {int printf(const char∗ ... );// ...}Given this <cstdio>, we could provide backward compatibility by adding a using-directive:// stdio.h:#include<cstdio>using namespace std;This <stdio.h> makes the Hello, world! program compile.
Unfortunately, the using-directive makesevery name from namespace std accessible in the global namespace. For example:#include<vector>vector v1;#include<stdio.h>vector v2;// carefully avoids polluting the global namespace// error: no ‘‘vector’’ in global scope// contains a ‘‘using namespace std;’’// oops: this now worksSo the standard requires that <stdio.h> place only names from <cstdio> in the global scope.