ZeePedia

Name Control:Static elements from C, Static initialization dependency, specifications

<< Inline Functions:Preprocessor pitfalls, Stash & Stack with inlines, Reducing clutter
References & the Copy-Constructor:The copyconstructor, Pointers to members >>
img
10: Name Control
Creating names is a fundamental activity in
programming, and when a project gets large, the
number of names can easily be overwhelming.
427
img
C++ allows you a great deal of control over the creation and
visibility of names, where storage for those names is placed, and
linkage for names.
The static keyword was overloaded in C before people knew what
the term "overload" meant, and C++ has added yet another
meaning. The underlying concept with all uses of static seems to be
"something that holds its position" (like static electricity), whether
that means a physical location in memory or visibility within a file.
In this chapter, you'll learn how static controls storage and
visibility, and an improved way to control access to names via
C++'s namespace feature. You'll also find out how to use functions
that were written and compiled in C.
Static elements from C
In both C and C++ the keyword static has two basic meanings,
which unfortunately often step on each other's toes:
1.
Allocated once at a fixed address; that is, the object is created
in a special static data area rather than on the stack each time a
function is called. This is the concept of static storage.
2.
Local to a particular translation unit (and local to a class
scope in C++, as you will see later). Here, static controls the
visibility of a name, so that name cannot be seen outside the
translation unit or class. This also describes the concept of
linkage, which determines what names the linker will see.
This section will look at the above meanings of static as they were
inherited from C.
static variables inside functions
When you create a local variable inside a function, the compiler
allocates storage for that variable each time the function is called by
428
Thinking in C++
img
moving the stack pointer down an appropriate amount. If there is
an initializer for the variable, the initialization is performed each
time that sequence point is passed.
Sometimes, however, you want to retain a value between function
calls. You could accomplish this by making a global variable, but
then that variable would not be under the sole control of the
function. C and C++ allow you to create a static object inside a
function; the storage for this object is not on the stack but instead in
the program's static data area. This object is initialized only once,
the first time the function is called, and then retains its value
between function invocations. For example, the following function
returns the next character in the array each time the function is
called:
//: C10:StaticVariablesInfunctions.cpp
#include "../require.h"
#include <iostream>
using namespace std;
char oneChar(const char* charArray = 0) {
static const char* s;
if(charArray) {
s = charArray;
return *s;
}
else
require(s, "un-initialized s");
if(*s == '\0')
return 0;
return *s++;
}
char* a = "abcdefghijklmnopqrstuvwxyz";
int main() {
// oneChar(); // require() fails
oneChar(a); // Initializes s to a
char c;
while((c = oneChar()) != 0)
cout << c << endl;
10: Name Control
429
img
} ///:~
The static char* sholds its value between calls of oneChar( )
because its storage is not part of the stack frame of the function, but
is in the static storage area of the program. When you call
oneChar( )with a char* argument, s is assigned to that argument,
and the first character of the array is returned. Each subsequent call
to oneChar( )without an argument produces the default value of
zero for charArray which indicates to the function that you are still
,
extracting characters from the previously initialized value of s. The
function will continue to produce characters until it reaches the null
terminator of the character array, at which point it stops
incrementing the pointer so it doesn't overrun the end of the array.
But what happens if you call oneChar( )with no arguments and
without previously initializing the value of s? In the definition for s,
you could have provided an initializer,
static char* s = 0;
but if you do not provide an initializer for a static variable of a
built-in type, the compiler guarantees that variable will be
initialized to zero (converted to the proper type) at program start-
up. So in oneChar( ) the first time the function is called, s is zero.
,
In this case, the if(!s) conditional will catch it.
The initialization above for s is very simple, but initialization for
static objects (like all other objects) can be arbitrary expressions
involving constants and previously declared variables and
functions.
You should be aware that the function above is very vulnerable to
multithreading problems; whenever you design functions
containing static variables you should keep multithreading issues
in mind.
430
Thinking in C++
img
static class objects inside functions
The rules are the same for static objects of user-defined types,
including the fact that some initialization is required for the object.
However, assignment to zero has meaning only for built-in types;
user-defined types must be initialized with constructor calls. Thus,
if you don't specify constructor arguments when you define the
static object, the class must have a default constructor. For example,
//: C10:StaticObjectsInFunctions.cpp
#include <iostream>
using namespace std;
class X {
int i;
public:
X(int ii = 0) : i(ii) {} // Default
~X() { cout << "X::~X()" << endl; }
};
void f() {
static X x1(47);
static X x2; // Default constructor required
}
int main() {
f();
} ///:~
The static objects of type X inside f( ) can be initialized either with
the constructor argument list or with the default constructor. This
construction occurs the first time control passes through the
definition, and only the first time.
Static object destructors
Destructors for static objects (that is, all objects with static storage,
not just local static objects as in the example above) are called when
main( ) exits or when the Standard C library function exit( ) is
explicitly called. In most implementations, main( ) just calls exit( )
when it terminates. This means that it can be dangerous to call
exit( ) inside a destructor because you can end up with infinite
10: Name Control
431
img
recursion. Static object destructors are not called if you exit the
program using the Standard C library function abort( )
.
You can specify actions to take place when leaving main( ) (or
calling exit( )) by using the Standard C library function atexit( ) In
.
this case, the functions registered by atexit( )may be called before
the destructors for any objects constructed before leaving main( )
(or calling exit( )).
Like ordinary destruction, destruction of static objects occurs in the
reverse order of initialization. However, only objects that have been
constructed are destroyed. Fortunately, the C++ development tools
keep track of initialization order and the objects that have been
constructed. Global objects are always constructed before main( ) is
entered and destroyed as main( ) exits, but if a function containing
a local static object is never called, the constructor for that object is
never executed, so the destructor is also not executed. For example,
//: C10:StaticDestructors.cpp
// Static object destructors
#include <fstream>
using namespace std;
ofstream out("statdest.out"); // Trace file
class Obj {
char c; // Identifier
public:
Obj(char cc) : c(cc) {
out << "Obj::Obj() for " << c << endl;
}
~Obj() {
out << "Obj::~Obj() for " << c << endl;
}
};
Obj a('a'); // Global (static storage)
// Constructor & destructor always called
void f() {
static Obj b('b');
}
432
Thinking in C++
img
void g() {
static Obj c('c');
}
int main() {
out << "inside main()" << endl;
f(); // Calls static constructor for b
// g() not called
out << "leaving main()" << endl;
} ///:~
In Obj, the char c acts as an identifier so the constructor and
destructor can print out information about the object they're
working on. The Obj a is a global object, so the constructor is
always called for it before main( ) is entered, but the constructors
for the static Obj binside f( ) and the static Obj cinside g( ) are
called only if those functions are called.
To demonstrate which constructors and destructors are called, only
f( ) is called. The output of the program is
Obj::Obj() for a
inside main()
Obj::Obj() for b
leaving main()
Obj::~Obj() for b
Obj::~Obj() for a
The constructor for a is called before main( ) is entered, and the
constructor for b is called only because f( ) is called. When main( )
exits, the destructors for the objects that have been constructed are
called in reverse order of their construction. This means that if g( )
is called, the order in which the destructors for b and c are called
depends on whether f( ) or g( ) is called first.
Notice that the trace file ofstreamobject out is also a static object ­
since it is defined outside of all functions, it lives in the static
storage area. It is important that its definition (as opposed to an
extern declaration) appear at the beginning of the file, before there
10: Name Control
433
img
is any possible use of out. Otherwise, you'll be using an object
before it is properly initialized.
In C++, the constructor for a global static object is called before
main( ) is entered, so you now have a simple and portable way to
execute code before entering main( ) and to execute code with the
destructor after exiting main( ). In C, this was always a trial that
required you to root around in the compiler vendor's assembly-
language startup code.
Controlling linkage
Ordinarily, any name at file scope (that is, not nested inside a class
or function) is visible throughout all translation units in a program.
This is often called external linkage because at link time the name is
visible to the linker everywhere, external to that translation unit.
Global variables and ordinary functions have external linkage.
There are times when you'd like to limit the visibility of a name.
You might like to have a variable at file scope so all the functions in
that file can use it, but you don't want functions outside that file to
see or access that variable, or to inadvertently cause name clashes
with identifiers outside the file.
An object or function name at file scope that is explicitly declared
static is local to its translation unit (in the terms of this book, the
cpp file where the declaration occurs). That name has internal
linkage. This means that you can use the same name in other
translation units without a name clash.
One advantage to internal linkage is that the name can be placed in
a header file without worrying that there will be a clash at link
time. Names that are commonly placed in header files, such as
const definitions and inline functions, default to internal linkage.
(However, const defaults to internal linkage only in C++; in C it
defaults to external linkage.) Note that linkage refers only to
434
Thinking in C++
img
elements that have addresses at link/load time; thus, class
declarations and local variables have no linkage.
Confusion
Here's an example of how the two meanings of static can cross over
each other. All global objects implicitly have static storage class, so
if you say (at file scope),
int a = 0;
then storage for a will be in the program's static data area, and the
initialization for a will occur once, before main( ) is entered. In
addition, the visibility of a is global across all translation units. In
terms of visibility, the opposite of static (visible only in this
translation unit) is extern, which explicitly states that the visibility
of the name is across all translation units. So the definition above is
equivalent to saying
extern int a = 0;
But if you say instead,
static int a = 0;
all you've done is change the visibility, so a has internal linkage.
The storage class is unchanged ­ the object resides in the static data
area whether the visibility is static or extern.
Once you get into local variables, static stops altering the visibility
and instead alters the storage class.
If you declare what appears to be a local variable as extern, it
means that the storage exists elsewhere (so the variable is actually
global to the function). For example:
//: C10:LocalExtern.cpp
//{L} LocalExtern2
#include <iostream>
int main() {
10: Name Control
435
img
extern int i;
std::cout << i;
} ///:~
//: C10:LocalExtern2.cpp {O}
int i = 5;
///:~
With function names (for non-member functions), static and extern
can only alter visibility, so if you say
extern void f();
it's the same as the unadorned declaration
void f();
and if you say,
static void f();
it means f( ) is visible only within this translation unit ­ this is
sometimes called file static.
Other storage class specifiers
You will see static and extern used commonly. There are two other
storage class specifiers that occur less often. The auto specifier is
almost never used because it tells the compiler that this is a local
variable. auto is short for "automatic" and it refers to the way the
compiler automatically allocates storage for the variable. The
compiler can always determine this fact from the context in which
the variable is defined, so auto is redundant.
A registervariable is a local (auto) variable, along with a hint to the
compiler that this particular variable will be heavily used so the
compiler ought to keep it in a register if it can. Thus, it is an
optimization aid. Various compilers respond differently to this
hint; they have the option to ignore it. If you take the address of the
variable, the registerspecifier will almost certainly be ignored. You
436
Thinking in C++
img
should avoid using registerbecause the compiler can usually do a
better job of optimization than you.
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:
10: Name Control
437
img
·
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 { /* ... */ };
438
Thinking in C++
img
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:
10: Name Control
439
img
//: 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();
440
Thinking in C++
img
};
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 namespacekeyword 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; }
// ...
};
10: Name Control
441
img
}
#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"
442
Thinking in C++
img
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.
10: Name Control
443
img
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
444
Thinking in C++
img
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
10: Name Control
445
img
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++.
Static members in C++
There are times when you need a single storage space to be used by
all objects of a class. In C, you would use a global variable, but this
is not very safe. Global data can be modified by anyone, and its
name can clash with other identical names in a large project. It
would be ideal if the data could be stored as if it were global, but be
hidden inside a class, and clearly associated with that class.
This is accomplished with static data members inside a class. There
is a single piece of storage for a static data member, regardless of
how many objects of that class you create. All objects share the
same static storage space for that data member, so it is a way for
them to "communicate" with each other. But the static data belongs
to the class; its name is scoped inside the class and it can be public,
private, or protected
.
Defining storage for static data members
Because static data has a single piece of storage regardless of how
many objects are created, that storage must be defined in a single
446
Thinking in C++
img
place. The compiler will not allocate storage for you. The linker will
report an error if a static data member is declared but not defined.
The definition must occur outside the class (no inlining is allowed),
and only one definition is allowed. Thus, it is common to put it in
the implementation file for the class. The syntax sometimes gives
people trouble, but it is actually quite logical. For example, if you
create a static data member inside a class like this:
class A {
static int i;
public:
//...
};
Then you must define storage for that static data member in the
definition file like this:
int A::i = 1;
If you were to define an ordinary global variable, you would say
int i = 1;
but here, the scope resolution operator and the class name are used
to specify A::i.
Some people have trouble with the idea that A::i is private, and yet
here's something that seems to be manipulating it right out in the
open. Doesn't this break the protection mechanism? It's a
completely safe practice for two reasons. First, the only place this
initialization is legal is in the definition. Indeed, if the static data
were an object with a constructor, you would call the constructor
instead of using the = operator. Second, once the definition has
been made, the end-user cannot make a second definition ­ the
linker will report an error. And the class creator is forced to create
the definition or the code won't link during testing. This ensures
that the definition happens only once and that it's in the hands of
the class creator.
10: Name Control
447
img
The entire initialization expression for a static member is in the
scope of the class. For example,
//: C10:Statinit.cpp
// Scope of static initializer
#include <iostream>
using namespace std;
int x = 100;
class WithStatic {
static int x;
static int y;
public:
void print() const {
cout << "WithStatic::x = " << x << endl;
cout << "WithStatic::y = " << y << endl;
}
};
int WithStatic::x = 1;
int WithStatic::y = x + 1;
// WithStatic::x NOT ::x
int main() {
WithStatic ws;
ws.print();
} ///:~
Here, the qualification WithStatic::extends the scope of WithStatic
to the entire definition.
static array initialization
Chapter 8 introduced the static constvariable that allows you to
define a constant value inside a class body. It's also possible to
create arrays of static objects, both const and non-const. The syntax
is reasonably consistent:
//: C10:StaticArray.cpp
// Initializing static arrays in classes
class Values {
// static consts are initialized in-place:
static const int scSize = 100;
448
Thinking in C++
img
static const long scLong = 100;
// Automatic counting works with static arrays.
// Arrays, Non-integral and non-const statics
// must be initialized externally:
static const int scInts[];
static const long scLongs[];
static const float scTable[];
static const char scLetters[];
static int size;
static const float scFloat;
static float table[];
static char letters[];
};
int Values::size = 100;
const float Values::scFloat = 1.1;
const int Values::scInts[] = {
99, 47, 33, 11, 7
};
const long Values::scLongs[] = {
99, 47, 33, 11, 7
};
const float Values::scTable[] = {
1.1, 2.2, 3.3, 4.4
};
const char Values::scLetters[] = {
'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j'
};
float Values::table[4] = {
1.1, 2.2, 3.3, 4.4
};
char Values::letters[10] = {
'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j'
};
int main() { Values v; } ///:~
10: Name Control
449
img
With static const of integral types you can provide the definitions
s
inside the class, but for everything else (including arrays of integral
types, even if they are static const you must provide a single
)
external definition for the member. These definitions have internal
linkage, so they can be placed in header files. The syntax for
initializing static arrays is the same as for any aggregate, including
automatic counting.
You can also create static constobjects of class types and arrays of
such objects. However, you cannot initialize them using the "inline
syntax" allowed for static consts of integral built-in types:
//: C10:StaticObjectArrays.cpp
// Static arrays of class objects
class X {
int i;
public:
X(int ii) : i(ii) {}
};
class Stat {
// This doesn't work, although
// you might want it to:
//!  static const X x(100);
// Both const and non-const static class
// objects must be initialized externally:
static X x2;
static X xTable2[];
static const X x3;
static const X xTable3[];
};
X Stat::x2(100);
X Stat::xTable2[] = {
X(1), X(2), X(3), X(4)
};
const X Stat::x3(100);
const X Stat::xTable3[] = {
X(1), X(2), X(3), X(4)
450
Thinking in C++
img
};
int main() { Stat v; } ///:~
The initialization of both const and non-const static arrays of class
objects must be performed the same way, following the typical
static definition syntax.
Nested and local classes
You can easily put static data members in classes that are nested
inside other classes. The definition of such members is an intuitive
and obvious extension ­ you simply use another level of scope
resolution. However, you cannot have static data members inside
local classes (a local class is a class defined inside a function). Thus,
//: C10:Local.cpp
// Static members & local classes
#include <iostream>
using namespace std;
// Nested class CAN have static data members:
class Outer {
class Inner {
static int i; // OK
};
};
int Outer::Inner::i = 47;
// Local class cannot have static data members:
void f() {
class Local {
public:
//! static int i;  // Error
// (How would you define i?)
} x;
}
int main() { Outer x; f(); } ///:~
10: Name Control
451
img
You can see the immediate problem with a static member in a local
class: How do you describe the data member at file scope in order
to define it? In practice, local classes are used very rarely.
static member functions
You can also create static member functions that, like static data
members, work for the class as a whole rather than for a particular
object of a class. Instead of making a global function that lives in
and "pollutes" the global or local namespace, you bring the
function inside the class. When you create a static member
function, you are expressing an association with a particular class.
You can call a static member function in the ordinary way, with the
dot or the arrow, in association with an object. However, it's more
typical to call a static member function by itself, without any
specific object, using the scope-resolution operator, like this:
//: C10:SimpleStaticMemberFunction.cpp
class X {
public:
static void f(){};
};
int main() {
X::f();
} ///:~
When you see static member functions in a class, remember that the
designer intended that function to be conceptually associated with
the class as a whole.
A static member function cannot access ordinary data members,
only static data members. It can call only other static member
functions. Normally, the address of the current object (this) is
quietly passed in when any member function is called, but a static
member has no this, which is the reason it cannot access ordinary
members. Thus, you get the tiny increase in speed afforded by a
global function because a static member function doesn't have the
452
Thinking in C++
img
extra overhead of passing this. At the same time you get the
benefits of having the function inside the class.
For data members, static indicates that only one piece of storage for
member data exists for all objects of a class. This parallels the use of
static to define objects inside a function to mean that only one copy
of a local variable is used for all calls of that function.
Here's an example showing static data members and static member
functions used together:
//: C10:StaticMemberFunctions.cpp
class X {
int i;
static int j;
public:
X(int ii = 0) : i(ii) {
// Non-static member function can access
// static member function or data:
j = i;
}
int val() const { return i; }
static int incr() {
//! i++; // Error: static member function
// cannot access non-static member data
return ++j;
}
static int f() {
//! val(); // Error: static member function
// cannot access non-static member function
return incr(); // OK -- calls static
}
};
int X::j = 0;
int main() {
X x;
X* xp = &x;
x.f();
xp->f();
X::f(); // Only works with static members
} ///:~
10: Name Control
453
img
Because they have no this pointer, static member functions can
neither access non-static data members nor call non-static member
functions.
Notice in main( ) that a static member can be selected using the
usual dot or arrow syntax, associating that function with an object,
but also with no object (because a static member is associated with
a class, not a particular object), using the class name and scope
resolution operator.
Here's an interesting feature: Because of the way initialization
happens for static member objects, you can put a static data
member of the same class inside that class. Here's an example that
allows only a single object of type Egg to exist by making the
constructor private. You can access that object, but you can't create
any new Egg objects:
//: C10:Singleton.cpp
// Static member of same type, ensures that
// only one object of this type exists.
// Also referred to as the "singleton" pattern.
#include <iostream>
using namespace std;
class Egg {
static Egg e;
int i;
Egg(int ii) : i(ii) {}
Egg(const Egg&); // Prevent copy-construction
public:
static Egg* instance() { return &e; }
int val() const { return i; }
};
Egg Egg::e(47);
int main() {
//!  Egg x(1); // Error -- can't create an Egg
// You can access the single instance:
cout << Egg::instance()->val() << endl;
} ///:~
454
Thinking in C++
img
The initialization for E happens after the class declaration is
complete, so the compiler has all the information it needs to
allocate storage and make the constructor call.
To completely prevent the creation of any other objects, something
else has been added: a second private constructor called the copy-
constructor. At this point in the book, you cannot know why this is
necessary since the copy constructor will not be introduced until
the next chapter. However, as a sneak preview, if you were to
remove the copy-constructor defined in the example above, you'd
be able to create an Egg object like this:
Egg e = *Egg::instance();
Egg e2(*Egg::instance());
Both of these use the copy-constructor, so to seal off that possibility
the copy-constructor is declared as private (no definition is
necessary because it never gets called). A large portion of the next
chapter is a discussion of the copy-constructor so it should become
clear to you then.
Static initialization dependency
Within a specific translation unit, the order of initialization of static
objects is guaranteed to be the order in which the object definitions
appear in that translation unit. The order of destruction is
guaranteed to be the reverse of the order of initialization.
However, there is no guarantee concerning the order of
initialization of static objects across translation units, and the
language provides no way to specify this order. This can cause
significant problems. As an example of an instant disaster (which
will halt primitive operating systems and kill the process on
sophisticated ones), if one file contains
// First file
#include <fstream>
10: Name Control
455
img
.
std::ofstream out("out.txt");
and another file uses the out object in one of its initializers
// Second file
#include <fstream>
extern std::ofstream out;
class Oof {
public:
Oof() { std::out << "ouch"; }
} oof;
the program may work, and it may not. If the programming
environment builds the program so that the first file is initialized
before the second file, then there will be no problem. However, if
the second file is initialized before the first, the constructor for Oof
relies upon the existence of out, which hasn't been constructed yet
and this causes chaos.
This problem only occurs with static object initializers that depend
on each other. The statics in a translation unit are initialized before
the first invocation of a function in that unit ­ but it could be after
main( ). You can't be sure about the order of initialization of static
objects if they're in different files.
A subtler example can be found in the ARM.1 In one file you have
at the global scope:
extern int y;
int x = y + 1;
and in a second file you have at the global scope:
extern int x;
int y = x + 1;
1Bjarne Stroustrup and Margaret Ellis, The Annotated C++ Reference Manual, Addison-
Wesley, 1990, pp. 20-21.
456
Thinking in C++
img
For all static objects, the linking-loading mechanism guarantees a
static initialization to zero before the dynamic initialization
specified by the programmer takes place. In the previous example,
zeroing of the storage occupied by the fstream outobject has no
special meaning, so it is truly undefined until the constructor is
called. However, with built-in types, initialization to zero does have
meaning, and if the files are initialized in the order they are shown
above, y begins as statically initialized to zero, so x becomes one,
and y is dynamically initialized to two. However, if the files are
initialized in the opposite order, x is statically initialized to zero, y
is dynamically initialized to one, and x then becomes two.
Programmers must be aware of this because they can create a
program with static initialization dependencies and get it working
on one platform, but move it to another compiling environment
where it suddenly, mysteriously, doesn't work.
What to do
There are three approaches to dealing with this problem:
1.
Don't do it. Avoiding static initialization dependencies is the
best solution.
2.
If you must do it, put the critical static object definitions in a
single file, so you can portably control their initialization by
putting them in the correct order.
3.
If you're convinced it's unavoidable to scatter static objects
across translation units ­ as in the case of a library, where
you can't control the programmer who uses it ­ there are two
programmatic techniques to solve the problem.
Technique one
This technique was pioneered by Jerry Schwarz while creating the
iostream library (because the definitions for cin, cout, and cerr are
static and live in a separate file). It's actually inferior to the second
technique but it's been around a long time and so you may come
10: Name Control
457
img
across code that uses it; thus it's important that you understand
how it works.
This technique requires an additional class in your library header
file. This class is responsible for the dynamic initialization of your
library's static objects. Here is a simple example:
//: C10:Initializer.h
// Static initialization technique
#ifndef INITIALIZER_H
#define INITIALIZER_H
#include <iostream>
extern int x; // Declarations, not definitions
extern int y;
class Initializer {
static int initCount;
public:
Initializer() {
std::cout << "Initializer()" << std::endl;
// Initialize first time only
if(initCount++ == 0) {
std::cout << "performing initialization"
<< std::endl;
x = 100;
y = 200;
}
}
~Initializer() {
std::cout << "~Initializer()" << std::endl;
// Clean up last time only
if(--initCount == 0) {
std::cout << "performing cleanup"
<< std::endl;
// Any necessary cleanup here
}
}
};
// The following creates one object in each
// file where Initializer.h is included, but that
// object is only visible within that file:
static Initializer init;
458
Thinking in C++
img
#endif // INITIALIZER_H ///:~
The declarations for x and y announce only that these objects exist,
but they don't allocate storage for the objects. However, the
definition for the Initializer init
allocates storage for that object in
every file where the header is included. But because the name is
static (controlling visibility this time, not the way storage is
allocated; storage is at file scope by default), it is visible only within
that translation unit, so the linker will not complain about multiple
definition errors.
Here is the file containing the definitions for x, y, and initCount
:
//: C10:InitializerDefs.cpp {O}
// Definitions for Initializer.h
#include "Initializer.h"
// Static initialization will force
// all these values to zero:
int x;
int y;
int Initializer::initCount;
///:~
(Of course, a file static instance of init is also placed in this file
when the header is included.) Suppose that two other files are
created by the library user:
//: C10:Initializer.cpp {O}
// Static initialization
#include "Initializer.h"
///:~
and
//: C10:Initializer2.cpp
//{L} InitializerDefs Initializer
// Static initialization
#include "Initializer.h"
using namespace std;
int main() {
cout << "inside main()" << endl;
10: Name Control
459
img
cout << "leaving main()" << endl;
} ///:~
Now it doesn't matter which translation unit is initialized first. The
first time a translation unit containing Initializer.his initialized,
initCountwill be zero so the initialization will be performed. (This
depends heavily on the fact that the static storage area is set to zero
before any dynamic initialization takes place.) For all the rest of the
translation units, initCountwill be nonzero and the initialization
will be skipped. Cleanup happens in the reverse order, and
~Initializer( )ensures that it will happen only once.
This example used built-in types as the global static objects. The
technique also works with classes, but those objects must then be
dynamically initialized by the Initializerclass. One way to do this
is to create the classes without constructors and destructors, but
instead with initialization and cleanup member functions using
different names. A more common approach, however, is to have
pointers to objects and to create them using new inside
Initializer( .)
Technique two
Long after technique one was in use, someone (I don't know who)
came up with the technique explained in this section, which is
much simpler and cleaner than technique one. The fact that it took
so long to discover is a tribute to the complexity of C++.
This technique relies on the fact that static objects inside functions
are initialized the first time (only) that the function is called. Keep
in mind that the problem we're really trying to solve here is not
when the static objects are initialized (that can be controlled
separately) but rather making sure that the initialization happens in
the proper order.
This technique is very neat and clever. For any initialization
dependency, you place a static object inside a function that returns
a reference to that object. This way, the only way you can access the
460
Thinking in C++
img
static object is by calling the function, and if that object needs to
access other static objects on which it is dependent it must call their
functions. And the first time a function is called, it forces the
initialization to take place. The order of static initialization is
guaranteed to be correct because of the design of the code, not
because of an arbitrary order established by the linker.
To set up an example, here are two classes that depend on each
other. The first one contains a bool that is initialized only by the
constructor, so you can tell if the constructor has been called for a
static instance of the class (the static storage area is initialized to
zero at program startup, which produces a false value for the bool
if the constructor has not been called):
//: C10:Dependency1.h
#ifndef DEPENDENCY1_H
#define DEPENDENCY1_H
#include <iostream>
class Dependency1 {
bool init;
public:
Dependency1() : init(true) {
std::cout << "Dependency1 construction"
<< std::endl;
}
void print() const {
std::cout << "Dependency1 init: "
<< init << std::endl;
}
};
#endif // DEPENDENCY1_H ///:~
The constructor also announces when it is being called, and you
can print( )the state of the object to find out if it has been
initialized.
The second class is initialized from an object of the first class, which
is what will cause the dependency:
//: C10:Dependency2.h
10: Name Control
461
img
#ifndef DEPENDENCY2_H
#define DEPENDENCY2_H
#include "Dependency1.h"
class Dependency2 {
Dependency1 d1;
public:
Dependency2(const Dependency1& dep1): d1(dep1){
std::cout << "Dependency2 construction ";
print();
}
void print() const { d1.print(); }
};
#endif // DEPENDENCY2_H ///:~
The constructor announces itself and prints the state of the d1
object so you can see if it has been initialized by the time the
constructor is called.
To demonstrate what can go wrong, the following file first puts the
static object definitions in the wrong order, as they would occur if
the linker happened to initialize the Dependency2object before the
Dependency1object. Then the order is reversed to show how it
works correctly if the order happens to be "right." Lastly, technique
two is demonstrated.
To provide more readable output, the function separator( )is
created. The trick is that you can't call a function globally unless
that function is being used to perform the initialization of a
variable, so separator( )returns a dummy value that is used to
initialize a couple of global variables.
//: C10:Technique2.cpp
#include "Dependency2.h"
using namespace std;
// Returns a value so it can be called as
// a global initializer:
int separator() {
cout << "---------------------" << endl;
return 1;
462
Thinking in C++
img
}
// Simulate the dependency problem:
extern Dependency1 dep1;
Dependency2 dep2(dep1);
Dependency1 dep1;
int x1 = separator();
// But if it happens in this order it works OK:
Dependency1 dep1b;
Dependency2 dep2b(dep1b);
int x2 = separator();
// Wrapping static objects in functions succeeds
Dependency1& d1() {
static Dependency1 dep1;
return dep1;
}
Dependency2& d2() {
static Dependency2 dep2(d1());
return dep2;
}
int main() {
Dependency2& dep2 = d2();
} ///:~
The functions d1( ) and d2( ) wrap static instances of Dependency1
and Dependency2objects. Now, the only way you can get to the
static objects is by calling the functions and that forces static
initialization on the first function call. This means that initialization
is guaranteed to be correct, which you'll see when you run the
program and look at the output.
Here's how you would actually organize the code to use the
technique. Ordinarily, the static objects would be defined in
separate files (because you're forced to for some reason; remember
that defining the static objects in separate files is what causes the
problem), so instead you define the wrapping functions in separate
files. But they'll need to be declared in header files:
10: Name Control
463
img
//: C10:Dependency1StatFun.h
#ifndef DEPENDENCY1STATFUN_H
#define DEPENDENCY1STATFUN_H
#include "Dependency1.h"
extern Dependency1& d1();
#endif // DEPENDENCY1STATFUN_H ///:~
Actually, the "extern" is redundant for the function declaration.
Here's the second header file:
//: C10:Dependency2StatFun.h
#ifndef DEPENDENCY2STATFUN_H
#define DEPENDENCY2STATFUN_H
#include "Dependency2.h"
extern Dependency2& d2();
#endif // DEPENDENCY2STATFUN_H ///:~
Now, in the implementation files where you would previously
have placed the static object definitions, you instead place the
wrapping function definitions:
//: C10:Dependency1StatFun.cpp {O}
#include "Dependency1StatFun.h"
Dependency1& d1() {
static Dependency1 dep1;
return dep1;
} ///:~
Presumably, other code might also be placed in these files. Here's
the other file:
//: C10:Dependency2StatFun.cpp {O}
#include "Dependency1StatFun.h"
#include "Dependency2StatFun.h"
Dependency2& d2() {
static Dependency2 dep2(d1());
return dep2;
} ///:~
So now there are two files that could be linked in any order and if
they contained ordinary static objects could produce any order of
initialization. But since they contain the wrapping functions, there's
no threat of incorrect initialization:
464
Thinking in C++
img
//: C10:Technique2b.cpp
//{L} Dependency1StatFun Dependency2StatFun
#include "Dependency2StatFun.h"
int main() { d2(); } ///:~
When you run this program you'll see that the initialization of the
Dependency1static object always happens before the initialization
of the Dependency2static object. You can also see that this is a
much simpler approach than technique one.
You might be tempted to write d1( ) and d2( ) as inline functions
inside their respective header files, but this is something you must
definitely not do. An inline function can be duplicated in every file
in which it appears ­ and this duplication includes the static object
definition. Because inline functions automatically default to
internal linkage, this would result in having multiple static objects
across the various translation units, which would certainly cause
problems. So you must ensure that there is only one definition of
each wrapping function, and this means not making the wrapping
functions inline.
Alternate linkage specifications
What happens if you're writing a program in C++ and you want to
use a C library? If you make the C function declaration,
float f(int a, char b);
the C++ compiler will decorate this name to something like
_f_int_charto support function overloading (and type-safe
linkage). However, the C compiler that compiled your C library has
most definitely not decorated the name, so its internal name will be
_f. Thus, the linker will not be able to resolve your C++ calls to f( ).
The escape mechanism provided in C++ is the alternate linkage
specification, which was produced in the language by overloading
the extern keyword. The extern is followed by a string that
10: Name Control
465
img
specifies the linkage you want for the declaration, followed by the
declaration:
extern "C" float f(int a, char b);
This tells the compiler to give C linkage to f( ) so that the compiler
doesn't decorate the name. The only two types of linkage
specifications supported by the standard are "C" and "C++," but
compiler vendors have the option of supporting other languages in
the same way.
If you have a group of declarations with alternate linkage, put them
inside braces, like this:
extern "C" {
float f(int a, char b);
double d(int a, char b);
}
Or, for a header file,
extern "C" {
#include "Myheader.h"
}
Most C++ compiler vendors handle the alternate linkage
specifications inside their header files that work with both C and
C++, so you don't have to worry about it.
Summary
The static keyword can be confusing because in some situations it
controls the location of storage, and in others it controls visibility
and linkage of a name.
With the introduction of C++ namespaces, you have an improved
and more flexible alternative to control the proliferation of names
in large projects.
466
Thinking in C++
img
The use of static inside classes is one more way to control names in
a program. The names do not clash with global names, and the
visibility and access is kept within the program, giving you greater
control in the maintenance of your code.
Exercises
Solutions to selected exercises can be found in the electronic document The Thinking in C++ Annotated
Solution Guide, available for a small fee from .
1.
Create a function with a static variable that is a pointer
(with a default argument of zero). When the caller
provides a value for this argument it is used to point at
the beginning of an array of int. If you call the function
with a zero argument (using the default argument), the
function returns the next value in the array, until it sees a
"-1" value in the array (to act as an end-of-array
indicator). Exercise this function in main( ).
2.
Create a function that returns the next value in a
Fibonacci sequence every time you call it. Add an
argument that is a bool with a default value of false such
that when you give the argument with true it "resets" the
function to the beginning of the Fibonacci sequence.
Exercise this function in main( ).
3.
Create a class that holds an array of ints. Set the size of
the array using static const int
inside the class. Add a
const int variable, and initialize it in the constructor
initializer list; make the constructor inline. Add a static
int member variable and initialize it to a specific value.
Add a static member function that prints the static data
member. Add an inline member function called print( )
to print out all the values in the array and to call the
static member function. Exercise this class in main( ).
4.
Create a class called Monitor that keeps track of the
number of times that its incident( )member function has
been called. Add a print( )member function that displays
10: Name Control
467
img
the number of incidents. Now create a global function
(not a member function) containing a static Monitor
object. Each time you call the function it should call
incident( ) then print( )to display the incident count.
,
Exercise the function in main( ).
5.
Modify the Monitor class from Exercise 4 so that you can
decrement( )the incident count. Make a class Monitor2
that takes as a constructor argument a pointer to a
Monitor1 and which stores that pointer and calls
,
incident( )and print( ) In the destructor for Monitor2
.
,
call decrement( )and print( ) Now make a static object
.
of Monitor2inside a function. Inside main( ), experiment
with calling the function and not calling the function to
see what happens with the destructor of Monitor2
.
6.
Make a global object of Monitor2and see what happens.
7.
Create a class with a destructor that prints a message and
then calls exit( ). Create a global object of this class and
see what happens.
8.
In StaticDestructors.cppexperiment with the order of
,
constructor and destructor calls by calling f( ) and g( )
inside main( ) in different orders. Does your compiler get
it right?
9.
In StaticDestructors.cpptest the default error handling
,
of your implementation by turning the original definition
of out into an extern declaration and putting the actual
definition after the definition of a (whose Obj constructor
sends information to out). Make sure there's nothing else
important running on your machine when you run the
program or that your machine will handle faults
robustly.
10.
Prove that file static variables in header files don't clash
with each other when included in more than one cpp file.
11.
Create a simple class containing an int, a constructor that
initializes the int from its argument, a member function
to set the int from its argument, and a print( )function
that prints the int. Put your class in a header file, and
468
Thinking in C++
img
include the header file in two cpp files. In one cpp file
make an instance of your class, and in the other declare
that identifier extern and test it inside main( ).
Remember, you'll have to link the two object files or else
the linker won't find the object.
12.
Make the instance of the object in Exercise 11 static and
verify that it cannot be found by the linker because of
this.
13.
Declare a function in a header file. Define the function in
one cpp file and call it inside main( ) in a second cpp file.
Compile and verify that it works. Now change the
function definition so that it is static and verify that the
linker cannot find it.
14.
Modify Volatile.cppfrom Chapter 8 to make
comm::isr( )something that could actually work as an
interrupt service routine. Hint: an interrupt service
routine doesn't take any arguments.
15.
Write and compile a simple program that uses the auto
and registerkeywords.
16.
Create a header file containing a namespace Inside the
.
namespacecreate several function declarations. Now
create a second header file that includes the first one and
continues the namespace adding several more function
,
declarations. Now create a cpp file that includes the
second header file. Alias your namespace to another
(shorter) name. Inside a function definition, call one of
your functions using scope resolution. Inside a separate
function definition, write a using directive to introduce
your namespace into that function scope, and show that
you don't need scope resolution to call the functions from
your namespace.
17.
Create a header file with an unnamed namespace.
Include the header in two separate cpp files and show
that an unnamed space is unique for each translation
unit.
10: Name Control
469
img
18.
Using the header file from Exercise 17, show that the
names in an unnamed namespace are automatically
available in a translation unit without qualification.
19.
Modify FriendInjection.cpp add a definition for the
to
friend function and to call the function inside main( ).
20.
In Arithmetic.cpp demonstrate that the using directive
,
does not extend outside the function in which the
directive was made.
21.
Repair the problem in OverridingAmbiguity.cppfirst
,
with scope resolution, then instead with a using
declaration that forces the compiler to choose one of the
identical function names.
22.
In two header files, create two namespaces, each
containing a class (with all inline definitions) with a
name identical to that in the other namespace. Create a
cpp file that includes both header files. Create a function,
and inside the function use the using directive to
introduce both namespaces. Try creating an object of the
class and see what happens. Make the using directives
global (outside of the function) to see if it makes any
difference. Repair the problem using scope resolution,
and create objects of both classes.
23.
Repair the problem in Exercise 22 with a using
declaration that forces the compiler to choose one of the
identical class names.
24.
Extract the namespace declarations in
BobsSuperDuperLibrary.cpp
and
UnnamedNamespaces.cpp
and put them in separate
header files, giving the unnamed namespace a name in
the process. In a third header file create a new namespace
that combines the elements of the other two namespaces
with using declarations. In main( ), introduce your new
namespace with a using directive and access all the
elements of your namespace.
25.
Create a header file that includes <string>and
<iostream>but does not use any using directives or
470
Thinking in C++
img
using declarations. Add "include guards" as you've seen
in the header files in this book. Create a class with all
inline functions that contains a string member, with a
constructor that initializes that string from its argument
and a print( )function that displays the string. Create a
cpp file and exercise your class in main( ).
26.
Create a class containing a static double and long. Write
a static member function that prints out the values.
27.
Create a class containing an int, a constructor that
initializes the int from its argument, and a print( )
function to display the int. Now create a second class
that contains a static object of the first one. Add a static
member function that calls the static object's print( )
function. Exercise your class in main( ).
28.
Create a class containing both a const and a non-const
static array of int. Write static methods to print out the
arrays. Exercise your class in main( ).
29.
Create a class containing a string, with a constructor that
initializes the string from its argument, and a print( )
function to display the string. Create another class that
contains both const and non-const static arrays of objects
of the first class, and static methods to print out these
arrays. Exercise this second class in main( ).
30.
Create a struct that contains an int and a default
constructor that initializes the int to zero. Make this
struct local to a function. Inside that function, create an
array of objects of your struct and demonstrate that each
int in the array has automatically been initialized to zero.
31.
Create a class that represents a printer connection, and
that only allows you to have one printer.
32.
In a header file, create a class Mirror that contains two
data members: a pointer to a Mirror object and a bool.
Give it two constructors: the default constructor
initializes the bool to true and the Mirror pointer to zero.
The second constructor takes as an argument a pointer to
a Mirror object, which it assigns to the object's internal
10: Name Control
471
img
pointer; it sets the bool to false. Add a member function
test( ): if the object's pointer is nonzero, it returns the
value of test( ) called through the pointer. If the pointer is
zero, it returns the bool. Now create five cpp files, each
of which includes the Mirror header. The first cpp file
defines a global Mirror object using the default
constructor. The second file declares the object in the first
file as extern, and defines a global Mirror object using
the second constructor, with a pointer to the first object.
Keep doing this until you reach the last file, which will
also contain a global object definition. In that file, main( )
should call the test( ) function and report the result. If the
result is true, find out how to change the linking order
for your linker and change it until the result is false.
33.
Repair the problem in Exercise 32 using technique one
shown in this book.
34.
Repair the problem in Exercise 32 using technique two
shown in this book.
35.
Without including a header file, declare the function
puts( ) from the Standard C Library. Call this function
from main( ).
472
Thinking in C++