Inapoi
Inainte
Cuprins
Namespaces
Although names can be nested inside
classes, the names of global functions, global variables, and classes are still
in a single global name space. The static keyword gives you some control
over this by allowing you to give variables and functions internal linkage (that
is, to make them file static).
But in a large project, lack of control over the global name space can cause
problems. To solve these problems for classes, vendors often create long
complicated names that are unlikely to clash, but then you’re stuck typing
those names. (A typedef is often used to simplify
this.) It’s not an elegant, language-supported solution.
You can subdivide the global name space
into more manageable pieces using the namespace
feature of C++. The
namespace keyword, similar to class,
struct, enum, and union, puts the names of its members in a
distinct space. While the other keywords have additional purposes, the creation
of a new name space is the only purpose for
namespace.
Creating a namespace
The creation of a namespace is notably
similar to the creation of a class:
//: C10:MyLib.cpp
namespace MyLib {
// Declarations
}
int main() {} ///:~
This produces a new namespace containing
the enclosed declarations. There are significant differences from class,
struct, union and enum,
however:
- A namespace definition can
appear only at global scope, or nested within another
namespace.
- No
terminating semicolon is necessary after the closing brace of a namespace
definition.
- A
namespace definition can be “continued” over multiple header files
using a syntax that, for a class, would appear to be a redefinition:
//: C10:Header1.h
#ifndef HEADER1_H
#define HEADER1_H
namespace MyLib {
extern int x;
void f();
// ...
}
#endif // HEADER1_H ///:~
//: C10:Header2.h
#ifndef HEADER2_H
#define HEADER2_H
#include "Header1.h"
// Add more names to MyLib
namespace MyLib { // NOT a redefinition!
extern int y;
void g();
// ...
}
#endif // HEADER2_H ///:~
//: C10:Continuation.cpp
#include "Header2.h"
int main() {} ///:~
- A namespace name can be
aliased to another name, so you don’t have to type an unwieldy name
created by a library vendor:
//: C10:BobsSuperDuperLibrary.cpp
namespace BobsSuperDuperLibrary {
class Widget { /* ... */ };
class Poppit { /* ... */ };
// ...
}
// Too much to type! I’ll alias it:
namespace Bob = BobsSuperDuperLibrary;
int main() {} ///:~
- You cannot create an
instance of a namespace as you can with a
class.
Unnamed namespaces
Each translation unit contains an unnamed
namespace that you can add to by
saying “namespace” without an identifier:
//: C10:UnnamedNamespaces.cpp
namespace {
class Arm { /* ... */ };
class Leg { /* ... */ };
class Head { /* ... */ };
class Robot {
Arm arm[4];
Leg leg[16];
Head head[3];
// ...
} xanthan;
int i, j, k;
}
int main() {} ///:~
The names in this space are automatically
available in that translation unit without qualification. It is guaranteed that
an unnamed space is unique for each translation unit. If you put local names in
an unnamed namespace, you don’t need to give them internal linkage by
making them static.
C++ deprecates the use of file statics in
favor of the unnamed namespace.
Friends
You can inject
a friend declaration into
a namespace by declaring it within an enclosed
class:
//: C10:FriendInjection.cpp
namespace Me {
class Us {
//...
friend void you();
};
}
int main() {} ///:~
Now the function you( ) is a
member of the namespace Me.
If you introduce a friend within a class
in the global namespace, the friend is injected
globally.
Using a namespace
You can refer to a name within a
namespace in three ways: by specifying the name using
the scope resolution operator, with a using directive to introduce all
names in the namespace, or with a using
declaration to introduce names
one at a time.
Scope resolution
Any name in a namespace can be explicitly
specified using the
scope
resolution operator in the same way that you can refer to the names within a
class:
//: C10:ScopeResolution.cpp
namespace X {
class Y {
static int i;
public:
void f();
};
class Z;
void func();
}
int X::Y::i = 9;
class X::Z {
int u, v, w;
public:
Z(int i);
int g();
};
X::Z::Z(int i) { u = v = w = i; }
int X::Z::g() { return u = v = w = 0; }
void X::func() {
X::Z a(1);
a.g();
}
int main(){} ///:~
Notice that the definition X::Y::i
could just as easily be referring to a data member of a class Y nested in
a class X instead of a namespace X.
So far, namespaces look very much like
classes.
The using directive
Because it can rapidly get tedious to
type the full qualification for an identifier in a namespace, the using
keyword allows you to import an entire namespace at once. When used in
conjunction with the namespace keyword this is called a
using
directive. The using
directive makes names appear as if they belong to the nearest enclosing
namespace scope, so you can conveniently use the unqualified names. Consider a
simple namespace:
//: C10:NamespaceInt.h
#ifndef NAMESPACEINT_H
#define NAMESPACEINT_H
namespace Int {
enum sign { positive, negative };
class Integer {
int i;
sign s;
public:
Integer(int ii = 0)
: i(ii),
s(i >= 0 ? positive : negative)
{}
sign getSign() const { return s; }
void setSign(sign sgn) { s = sgn; }
// ...
};
}
#endif // NAMESPACEINT_H ///:~
One use of the using directive is
to bring all of the names in Int into another namespace, leaving those
names nested within the namespace:
//: C10:NamespaceMath.h
#ifndef NAMESPACEMATH_H
#define NAMESPACEMATH_H
#include "NamespaceInt.h"
namespace Math {
using namespace Int;
Integer a, b;
Integer divide(Integer, Integer);
// ...
}
#endif // NAMESPACEMATH_H ///:~
You can also declare all of the names in
Int inside a function, but leave those names nested within the
function:
//: C10:Arithmetic.cpp
#include "NamespaceInt.h"
void arithmetic() {
using namespace Int;
Integer x;
x.setSign(positive);
}
int main(){} ///:~
Without the using directive, all
the names in the namespace would need to be fully qualified.
One aspect of the using directive
may seem slightly counterintuitive at first. The visibility of the names
introduced with a using directive is the scope in which the directive is
made. But you can override the names from the using directive as if
they’ve been declared globally to that scope!
//: C10:NamespaceOverriding1.cpp
#include "NamespaceMath.h"
int main() {
using namespace Math;
Integer a; // Hides Math::a;
a.setSign(negative);
// Now scope resolution is necessary
// to select Math::a :
Math::a.setSign(positive);
} ///:~
Suppose you have a second namespace that
contains some of the names in namespace Math:
//: C10:NamespaceOverriding2.h
#ifndef NAMESPACEOVERRIDING2_H
#define NAMESPACEOVERRIDING2_H
#include "NamespaceInt.h"
namespace Calculation {
using namespace Int;
Integer divide(Integer, Integer);
// ...
}
#endif // NAMESPACEOVERRIDING2_H ///:~
Since this namespace is also introduced
with a using directive, you have the possibility of a collision. However,
the ambiguity appears at the
point of use of the name, not at the using
directive:
//: C10:OverridingAmbiguity.cpp
#include "NamespaceMath.h"
#include "NamespaceOverriding2.h"
void s() {
using namespace Math;
using namespace Calculation;
// Everything's ok until:
//! divide(1, 2); // Ambiguity
}
int main() {} ///:~
Thus, it’s possible to write
using directives to introduce a number of namespaces with conflicting
names without ever producing an ambiguity.
The using declaration
You can inject names one at a time into
the current scope with a using
declaration.
Unlike the using directive, which treats names as if they were declared
globally to the scope, a using declaration is a declaration within the
current scope. This means it can override names from a using
directive:
//: C10:UsingDeclaration.h
#ifndef USINGDECLARATION_H
#define USINGDECLARATION_H
namespace U {
inline void f() {}
inline void g() {}
}
namespace V {
inline void f() {}
inline void g() {}
}
#endif // USINGDECLARATION_H ///:~
//: C10:UsingDeclaration1.cpp
#include "UsingDeclaration.h"
void h() {
using namespace U; // Using directive
using V::f; // Using declaration
f(); // Calls V::f();
U::f(); // Must fully qualify to call
}
int main() {} ///:~
The using declaration just gives
the fully specified name of the identifier, but no type information. This means
that if the namespace contains a set of
overloaded functions with the
same name, the using declaration declares all the functions in the
overloaded set.
You can put a using declaration
anywhere a normal declaration can occur. A using declaration works like a
normal declaration in all ways but one: because you don’t give an argument
list, it’s possible for a using declaration to cause the overload
of a function with the same argument types (which
isn’t allowed with normal overloading). This ambiguity, however,
doesn’t show up until the point of use, rather than the point of
declaration.
A using declaration can also
appear within a namespace, and it has the same effect as anywhere else –
that name is declared within the space:
//: C10:UsingDeclaration2.cpp
#include "UsingDeclaration.h"
namespace Q {
using U::f;
using V::g;
// ...
}
void m() {
using namespace Q;
f(); // Calls U::f();
g(); // Calls V::g();
}
int main() {} ///:~
A using declaration is an alias,
and it allows you to declare the same function in separate namespaces. If you
end up re-declaring the same function by importing different namespaces,
it’s OK – there won’t be any ambiguities or
duplications.
The use of namespaces
Some of the rules above may seem a bit
daunting at first, especially if you get the impression that you’ll be
using them all the time. In general, however, you can get away with very simple
usage of namespaces as long as you understand how they work. The key thing to
remember is that when you introduce a global using directive (via a
“using namespace” outside of any scope) you have thrown open
the namespace for that file. This is usually fine for an implementation file (a
“cpp” file) because the using directive is only in
effect until the end of the compilation of that file. That is, it doesn’t
affect any other files, so you can adjust the control of the namespaces one
implementation file at a time. For example, if you discover a name clash because
of too many using directives in a particular implementation file, it is a
simple matter to change that file so that it uses explicit qualifications or
using declarations to eliminate the clash, without modifying other
implementation files.
Header
files are a different issue. You virtually never want to introduce a global
using directive into a header file, because that would mean that any
other file that included your header would also have the namespace thrown open
(and header files can include other header files).
So, in header files you should either use
explicit qualification or scoped using directives and using
declarations. This is the practice that you will find in this book, and by
following it you will not “pollute” the global namespace and throw
yourself back into the pre-namespace world of C++.
 |
|