Storage

Initializer Lists

One of the original design goals of C++ was to eliminate the bug-infested gap between object creation and object initialization. When a C++ object is created, a user-defined or system-defined[1] constructor is automatically called to initialize the object's member variables.

An initializer list is a list of pre-initializations— member variable initializations that occur in the imperceptibly brief gap between object creation and the execution of the first line of the called constructor. Syntactically, the list is announced by a colon and is sandwiched between the header of a constructor and its body:

Class(PARAM, ... PARAM): INIT, ... INIT { STMT; ... STMT; }

Why not initialize all member variables inside the constructor's body? Because some member variables are in "hard to reach" places. For example, inherited member variables may have been declared private in the base class, and nested member variables— i.e., member variables of member objects —may have been declared private in the associated class.

Assume an Employee class has a private member variable called name of type string, which is initialized by Employee constructors. Assume a Manager class and a Secretary class are derived from the Employee class:

Secretaries have a private integer member variable called wpm, which represents typing speed in words per minute. Managers have a private Boolean member variable called canUseJet, which indicates if recreational use of the company jet is allowed on weekends. Each manager object also has a private member variable called sec of type Secretary, which represents the manager's personal secretary:

class Manager
{
   Secretary sec;
   bool canUseJet;
public:
   Manager(string mnm, string snm, int wpm, bool jet);
   // etc.
};

In addition to the explicitly declared canUseJet and sec variables, a manager object also encapsulates the inherited name variable as well as the nested sec.name and sec.wpm. For example, the declaration:

Manager smith("Smith", "Jones", 60, false);

might create an object in memory that looks like this:

But how can a Manager constructor initialize these implicit member variables? If we leave them uninitialized, then the Employee and Secretary default constructors— if they exist —will be automatically called to perform the pre-initializations, but this doesn't help us.

We can't simply initialize them in the constructor's body:

Manager::Manager(string mnm, string snm, int wpm, bool jet)
{
   name = mnm; // error! name is private
   sec.name = snm; // error! sec.name is private
   sec.wpm = wpm; // error! sec.wpm is private
   canUseJet = jet; // ok
}

The Manager constructor could create an anonymous temporary secretary object, then assign it to sec:

Manager::Manager(string mnm, string snm, int wpm, bool jet)
{
   sec = Secretary(snm, wpm); // ok, sec = temp
   Employee(mnm); // this does nothing!
   canUseJet = jet; // ok
}

But this can be inefficient if the default Secretary constructor, which is still automatically called to pre-initialize the sec variable, consumes a lot of time or memory, and of course we still have the problem of initializing the inherited name variable. Calling the Employee constructor inside the body of the Manager constructor only creates a temporary anonymous employee object with a name member variable that is unrelated to the inherited name member variable.

The solution to our problem is to use the initializer list to specify any non-default constructors that should be called to pre-initialize inherited and nested member variables:

Manager::Manager(string mnm, string snm, int wpm, bool jet)
: Employee(mnm), sec(snm, wpm)
{
   canUseJet = jet;
}

Notice that the Employee base class constructor is explicitly called, while the Secretary constructor is implicitly called by specifying the arguments after the sec variable.

Initializing Constant Members

In addition to inherited and nested private member variables, constant member variables also present an initialization problem. Constants are read-only variables. It's an error for a constant to appear on the left side of an assignment statement:

const double pi;
pi = 3.1416; // error!

So how do we initialize a constant? This must be done in its declaration:

const double pi = 3.1416; // ok, this is not an assignment

But then how do we initialize constant member variables like critical, the constant member of our nuclear reactor class from chapter 2?

class Reactor
{
   double temp; // = temperature of reactor's core
   const double critical; // critical < temp is too hot!
public:
   Reactor(double t = 0, double c = 1500);
   // etc.
};

Initializing critical in the constructor's body will be regarded as an illegal assignment statement by the compiler:

Reactor::Reactor(double t, double c)
{
   critical = c; // error! critical is read-only
   temp = t; // ok
}

Fortunately, we can also use the initializer list to pre-initialize constant members:

Reactor::Reactor(double t, double c)
: critical(c)
{
   temp = t; // ok
}

Recall that the initial value of a simple variable can be specified using C syntax:

double critical = c;

or C++ syntax:

double critical(c);

Initializer lists always use the latter form.

What is the advantage of having constant members? Why not simply define critical to be a global constant or a member of an anonymous, nested enumeration:

class Reactor
{
   enum { critical = 1500 };
   // etc.
};

Well for one thing, members of enumerations must be integers, not doubles. But more importantly, declaring critical to be a constant member means different reactors can have different critical temperatures.

References are essentially constant pointers, so we run into the same initialization problems we faced with constants. For example, assume reactors send notifications of temperature changes to a monitor that is represented by a reference to an output stream:

class Reactor
{
   double temp; // temperature of reactor's core
   const double critical; // critical < temp is too hot!
   ostream& monitor; // send temp change notifications here
public:
   Reactor(double t = 0, double c = 1500, ostream& m = cout);
   // etc.
};

Like constants, references must be initialized in the initializer list:

Reactor(double t, double c, ostream& m)
: critical(c), monitor(m)
{
   temp = t;
}

It turns out that all member variables can be initialized in an initializer list, leaving the constructor body empty:

Reactor(double t, double c, ostream& m)
: critical(c), monitor(m), temp(t)
{ } // no op!

Scope, Visibility, Extent, and Storage Class

A declaration creates a binding between a name and a bindable (e.g., a type, variable, or function). For example, the declaration:

int x;

creates a binding between the name x and an integer variable. (See Programming Note A2.2) Of course a name may be bound to multiple bindables, and a bindable may be bound to multiple names.

The visibility of a binding is the lexical region of the program where the binding is valid. A binding has global visibility if it is visible throughout the entire program, otherwise we say the binding has local visibility.

The extent of a binding (also called the lifetime of a binding) is the period of time that the binding exists. A binding has global extent if it exists from the moment of its declaration until the program terminates, otherwise we say the binding has local extent.

Generally speaking, global visibility implies global extent and local visibility implies local extent, although in some cases a binding with local visibility may have global extent. (Why would global visibility with local extent would be a recipe for disaster?)

Unfortunately, there is no simple or elegant mapping between these concepts and C/C++ terminology. C++ programmers speak of the scope of a name, rather than the visibility of a binding. Even the phrase "scope of a name" is inaccurate. What is really meant is the scope of a declaration, which simply refers to the file, namespace, class, or block in which the declaration occurs.

The scope of a declaration is loosely related to the visibility of the binding it creates. For example, a declaration with block scope creates a binding that is only visible from the point of declaration to the end of the block. A declaration with file scope creates a binding that is visible from the point of declaration to the end of the file, unless the binding is explicitly imported to another file. For example, placing the declaration:

const double pi = 3.1416;

in a source file means the binding between the name pi and the constant 3.1416 is visible from the point of declaration to the end of the file. However, we can refer to this binding in other files by including the pure declaration:

extern const double pi;

Recall that when a C or C++ program is loaded in memory, it is allocated four segments of memory:

In addition, program data can also be stored in the processor's registers. The storage class of a variable refers to the memory segment where the variable is located: register, heap, stack, or static. (C/C++ uses the term automatic to refer to stack variables.)

C++ programmers speak of the storage class of a variable rather than the extent of a binding. There is a loose relationship. For example, bindings to static variables have global extents, while bindings to automatic variables have local extents. (Heap variables come into existence when the new() operator is called and disappear when the delete() operator is called.)

The relationship between the storage class of a variable and the scope of variable's declaration is tricky. Normally, a variable declaration with file scope produces a binding to a static variable, while a variable declaration with block scope produces a binding to an automatic variable. However, we can override this by specifying the storage class in the declaration.

For example, suppose we want to define a function that returns the number of times it has been called. The following definition fails because we keep the call count in a variable created by a block-scope declaration which is created (and initialized to 0) each time the function is called, and is destroyed each time the function terminates:

int numCalls()
{
   int count = 0;
   return ++count; // wrong: always returns 1
}

We can remedy the situation by specifying the call count to be static:

int numCalls()
{
   static int count = 0; // local scope with global extent!
   return ++count;
}

Curiously, placing static in front of a file scope declaration limits the scope of the binding it creates to the file in which it is declared. I.e., it cannot be exported into other files.

Scope, visibility, extent, and storage class become very confusing when we think about member variables and functions. For example, consider the following class declaration:

class Account
{ 
public:
   Account(double bal = 0) { balance = bal; }
   void withdraw(double amt);
   void deposit(double amt);
   // etc.
protected:
   double balance;
};

All of the member declarations have class scope according to C++ terminology. However, this doesn't tell us much about visibility, extent, or storage class. The declaration of balance, for example, doesn't create a variable. No variable is created until we create some instances of the Account class:

Account a, b, c;

At this point three variables have been created: a.balance, b.balance, and c.balance. The extent and storage class of these variables will be the same as the extend and storage class of a, b, and c, respectively. The visibility of the bindings between these qualified names and the corresponding variables is restricted to member functions of the Account class and all classes derived from the Account class. This is because balance was declared protected.

The unqualified name balance, is bound to all three variables, but the visibility of each binding is restricted to member functions of the Account class and all classes derived from the Account class. In this case the restricted visibility has nothing to do with the fact that balance is a protected member variable, the visibility of the binding remains unchanged even if balance is public! (In truth, the compiler replaces all unqualified references to balance by the qualified reference this->balance.)

Static Members

Placing the static specifier in front of a member variable has two consequences. First, static member variables are allocated in the static memory segment, as expected. Second, static member variables are not encapsulated in objects! A static member variable is unique, and it has global extent: it comes into existence before main() begins execution, and disappears when main() terminates.

For example, suppose we want to keep track of the number of account objects that exist. It wouldn't make sense to store the count in an ordinary member variable. In this case every account object would encapsulate a count variable. But this leads to a chicken-versus-egg problem: the count variable only exists if account objects exist to encapsulate it. How could it ever be 0? Also, each time a new account is created or destroyed, we would have to update each remaining account's count variable. We could store the count in a global variable (i.e. a variable created by a file-scope declaration):

int Account_count = 0;

Of course file scope variables have global visibility, so there is a possibility of corruption. The best strategy is to use a private static member variable:

class Account
{ 
public:
   Account(double bal = 0) { balance = bal; count++; }
   Account(const Account& acct) { balance = acct.balance; count++; }
   ~Account() { count--; }
   void withdraw(double amt);
   void deposit(double amt);
   // etc.
private:
   double balance;
   static int count;
};

Notice that Account constructors increment count while the Account destructor decrements count. (Why was it necessary to define a new copy constructor?)

The chicken-versus-egg problem is solved because the extent of count is global, it isn't tied to the extent of any particular account object. The corruption problem is solved, too. The visibility of count is restricted to Account member functions because it is private.

How will clients access count? Outside of the class scope the variable goes by its qualified name: Account::count, but this doesn't help, because it is still a priavte variable. We could provide a public member function for this purpose:

int Account::getCount() { return count; }

But this leads back to the chicken-versus-egg problem. An account object must exists before we can call its getCount() member function.

Fortunately, member functions can also be declared static:

class Account
{ 
public:
   Account(double bal = 0) { balance = bal; count++; }
   Account(const Account& acct) { balance = acct.balance; count++; }
   ~Account() { count--; }
   void withdraw(double amt);
   void deposit(double amt);
   static int getCount() { return count; }
   // etc.
private:
   double balance;
   static int count;
};

Unlike ordinary member functions, static member functions don't have implicit parameters. This means they can be called even if no instance of the class exist. For example, here's how a client might display count:

cout << Account::getCount() << endl;

One last problem: how will Account::count get initialized to 0? We can't initialize it in an Account() constructor, because constructors are only called when objects are created and they are called each time an object is created. In fact, the declaration of count is only a pure declaration, it does not bind Account::count to any particular variable, it only says that it will be bound to a variable. We still need the definition:

int Account::count = 0;

This definition would normally be a file-scope declaration in a source file such as account.cpp.

Type Conversion

Sometimes strict type checking can limit the flexibility of an algorithm. For example, it would be a real headache if the cos() function refused to accept integer arguments:

cos(0); // error, argument must be a float?

Fortunately, the C++ compiler recognizes many of these situations and automatically converts the given value to an equivalent value of the expected type.

In some situations this might involve actually transforming the internal representation of the value. This is called coercion. For example, the C++ compiler automatically translates cos(0) to cos(0.0). Of course the internal representation of 0 is quite different from the internal representation of 0.0.

In other situations the internal representation doesn't need to change, it only needs to be temporarily retyped. For example, assume the following inheritance hierarchy has been defined:

Assume we define a pointer to an executive object:

Executive* smith = new Executive(...);

We can freely use this pointer in contexts where pointers to employees are expected:

Employee* employee = smith;

In this case the compiler simply retypes the pointer to the expected type. The actual value of the pointer doesn't need to change because a pointer to an Executive is— technically and logically —a pointer to an Employee.

C/C++ uses the term casting to refer to both coercion and retyping. Retyping an Executive pointer to an Employee pointer is called up casting because the target type is a base class of the original type. Up casts are performed automatically by the compiler, as required. A cast that is performed automatically is called an implicit cast.

By contrast, a down cast replaces the original type with a more specialized derived class. Of course this only makes sense if the pointer actually points to an instance of the more specialized class. For example, assume we need a single pointer that can be used to point at all types of employees. Only an Employee pointer allows this flexibility:

Employee* emp;

At one point in our program we point our emp pointer at an Executive object:

emp = new Executive(...);

Assigning an Executive pointer to an Employee pointer is an up cast, so the C++ compiler automatically performs the necessary retyping operation. Now suppose we want to find out the bonus of this employee using the Executive::getBonus() member function:

cout << emp->getBonus() << endl; // error

This generates a compile-time error because the C++ compiler has no way of knowing that emp actually points to an Executive object, and of course there is no Employee::getBonus() member function.

Of course we know that emp points to an Executive, so we can tell the compiler to perform the cast using the static_cast() operator:

Executive* exec = static_cast<Executive*>(emp);
cout << exec->getBonus() << endl; // okay

We can also use the traditional C syntax to perform static casts:

Executive* exec = (Executive*)emp;

If a type has a simple name, for example if we introduce ExecPointer as a synonym for pointers to Executives:

typedef Executive* ExecPointer;

then we can also use "function call" syntax to perform static casts:

Executive* exec = ExecPointer(emp);

In contrast to implicit casts, casts performed by the programmer— regardless of the syntax used —are called explicit casts.

We have to be careful, though.  If emp doesn't really point at an Employee, if emp points at a Programmer instead:

emp = new Programmer(...); // allowable upcast

then our static cast forces the compiler to point an Executive pointer at a Programmer object, and calling exec->getBonus() causes the program to crash or, worse yet, produces the incorrect answer.

We can guard against this type of error by using the dynamic cast operator, which uses runtime type information (RTTI, see chapter 6) to determine if the object is actually an instance of the target type. If not, then the null pointer, 0, is returned:

Executive* exec;
if (exec = dynamic_cast<Executive*>(emp))
   cout << exec->getBonus() << endl;
else
   cerr << "invalid cast\n";

Note: dynamic casting requires the base class to be virtual (i.e., at least one member function must be virtual, the destructor for example). Also, some compilers may require special RTTI options to be designated.

When we introduce multiple inheritance into our hierarchy, then we allow the possibility of cross casts. For example, assume a company's chief executive officer (CEO) must be an employee and a stock holder:

Assume an Employee pointer actually points at a CEO object:

Employee* gates = new CEO();

At some point in the program we may want to know how many shares of stock gates owns, but Employee is neither a base class nor a derived class of StockHolder, so neither an up cast nor a downcast will work in this situation, and consequently the static cast:

static_cast<StockHolder*>(gates);

causes a compile-time error. Retyping gates as a StockHolder is called a cross cast because the target type is a peer of the original type. Cross casts must be performed dynamically:

dynamic_cast<StockHolder*>(gates);

C++ also offers the reinterpret_cast() and const_cast() operators. The const_cast() operator is used to retype a variable of type const T* to a variable of type T*. For example:

void review(const Executive* exec)
{
   if (hasOtherOffers(exec))
   {
      Executive* temp = const_cast<Executive*>(exec);
      temp->setBonus(1000000);
   }
}

Of course review(smith) only changes smith.bonus if smith was not declared as a constant.

The reinterpret_cast() operator is the elephant gun of casting operators. Using it, we can force the compiler to perform static casts it would otherwise disallow. For example, the compiler would normally disallow a static cast from a pointer to an integer::

Executive* smith = new Executive();
int location = static_cast<int>(smith); // error!

But if we are real sure we know better than the compiler, then we can force the retyping operation:

int location = reinterpret_cast<int>(smith);

Programmer-Defined Coercions

We can declare a coercion from an existing type, T, to a programmer-defined class, C, by simply declaring a constructor for C that expects a single parameter of type T.

C::C(T t) { ... }

We can declare coercion from C to T by defining a member function for C that overloads the T() casting operator (i.e., the operator called by T(exp)):

C::operator T() { ... }

The C++ compiler will automatically call the appropriate coercion when it sees an instance of C appearing in a context where an instance of T is expected, and vice-versa.

For example, suppose we define a rational number class with two integer member variables representing numerator and denominator:

class Rational
{
   int num, den; // = num/den
public:
   Rational(int n = 0, int d = 1) { num = n; den = d; }
   // etc.
};

Every integer, n, can be interpreted as the rational n/1, therefore we would like to be able to use integers in contexts where rational numbers are expected. For example, we would like to be able to assign an integer to a Rational variable:

Rational r;
r = 6; // r = 6/1

In this case the compiler will automatically search for a Rational constructor that expects a single integer parameter. If it finds one, it will translate the assignment as:

r = Rational(6);

In our example, the compiler will use the constructor above with the default second argument:

r = Rational(6, 1); // r = 6/1

Every rational number can be approximated by a floating point number. For example, 7/6 could be approximated by 1.166660. In general, the rational n/m is approximated by the floating point result of dividing n by m. Therefore, we would like to be able to use rationals in contexts where floating point numbers are expected. For example, we may want to assign a rational number to a variable of type double:

Rational r(7, 6);
double x;
x = r; // x = 1.166660

Defining a Rational constructor that expects a single parameter of type double doesn't help, because it would coerce doubles to rationals, and we need to go the other way; we need to coerce rationals to doubles. This is easy to do if we realize that static casts such as:

double(r);

are actually calls to an operator that can be overloaded. The syntax is a little weird, though. The official name of the operator is "operator double()" (i.e., a blank space is part of the name!). Also, this operator must always return a double, so as with constructors, C++ doesn't allow programmers to specify the return type. (Otherwise the compiler would need to decide how to respond if the programmer specified the wrong return type.) Here is our definition of the double() operator for rationals:

class Rational
{
   int num, den; // = num/den
public:
   Rational(int n = 0, int d = 1) { num = n; den = d; }
   operator double() { return double(num)/double(den); }
   // etc.
};

The compiler will automatically translate the assignment given above to:

x = double(r);

 



[1] The system provides a default constructor and a copy constructor. Of course the default constructor disappears if there are any user-defined constructors.