Guaranteed initialization with the constructor
Both the Stash and Stack
classes defined previously have a function called initialize( ),
which hints by its name that it should be called before using the object in any
other way. Unfortunately, this means the client programmer must ensure proper
initialization. Client programmers are prone to miss details like initialization
in their headlong rush to make your amazing library solve their problem. In C++,
initialization is too important to leave to the client programmer. The class
designer can guarantee initialization of every object by providing a special
function called the
constructor. If a class
has a constructor, the compiler automatically calls that constructor at the
point an object is created, before client programmers can get their hands on the
object. The constructor call isn’t even an option for the client
programmer; it is performed by the compiler at the point the object
is defined.
The next challenge is what to name this
function. There are two issues. The first is that any name you use is something
that can potentially clash with a name you might like to use as a member in the
class. The second is that because the compiler is responsible for calling the
constructor, it must always know which function to call. The solution Stroustrup
chose seems the easiest and most logical: the name of
the constructor is the same as the name of the class. It
makes sense that such a function will be called automatically on
initialization.
Here’s a simple class with a
constructor:
class X {
int i;
public:
X(); // Constructor
};
Now, when an object is
defined,
void f() {
X a;
// ...
}
the same thing happens as if a
were an int: storage is allocated for the object. But when the program
reaches the sequence point
(point of execution) where
a is defined, the constructor is called automatically. That is, the
compiler quietly inserts the call to X::X( ) for the object a
at the point of definition. Like any member function, the first (secret)
argument to the constructor is the
this pointer – the
address of the object for which it is being called. In the case of the
constructor, however, this is pointing to an un-initialized block of
memory, and it’s the job of the constructor to initialize this memory
properly.
Like any function, the constructor can
have arguments to allow you to
specify how an object is created, give it initialization values, and so on.
Constructor arguments provide you with a way to guarantee that all parts of your
object are initialized to appropriate values. For example, if a class
Tree has a constructor that takes a single integer argument denoting the
height of the tree, then you must create a tree object like
this:
Tree t(12); // 12-foot tree
If Tree(int) is your only
constructor, the compiler won’t let you create an object any other way.
(We’ll look at multiple constructors and different ways to call
constructors in the next chapter.)
That’s really all there is to a
constructor; it’s a specially named function that is called automatically
by the compiler for every object at the point of that object’s creation.
Despite it’s simplicity, it is exceptionally valuable because it
eliminates a large class of problems and makes the code easier to write and
read. In the preceding code fragment, for example, you don’t see an
explicit function call to some initialize( ) function that is
conceptually separate from definition. In C++, definition and initialization are
unified concepts – you can’t have one without the
other.
Both the constructor and destructor are
very unusual types of functions: they have no return
value. This is distinctly
different from a void return value, in which the function returns nothing
but you still have the option to make it something else. Constructors and
destructors return nothing and you don’t have an option. The acts of
bringing an object into and out of the program are special, like birth and
death, and the compiler always makes the function calls itself, to make sure
they happen. If there were a return value, and if you could select your own, the
compiler would somehow have to know what to do with the return value, or the
client programmer would have to explicitly call constructors and destructors,
which would eliminate their
safety.