📄 chapter 11 constructors and destructors.htm
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0063)http://topaz.cs.byu.edu/text/html/Textbook/Chapter11/index.html -->
<HTML><HEAD><TITLE>Chapter 11: Constructors and Destructors</TITLE>
<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
<META content="MSHTML 6.00.2800.1458" name=GENERATOR></HEAD>
<BODY>
<CENTER>
<H1>Chapter 11<BR>Constructors and Destructors</H1></CENTER>
<HR>
<!-------------------------------------------------------------------------------->
<H2>11.1 Constructing a Class</H2><!-------------------------------------------------------------------------------->It
is important to realize throughout the material in this chapter that the
implementations discussed have reference to the SAL compiler, specifically. In
the design of OOP compilers, the implementation of class constructors is largely
left up to the vendor.
<P>It is also important to know that a constructor is very different from a
normal method. In OOP, members always take an implicit pointer to the instance
for which they were invoked. Not so with constructors. A constructor is a
function that is invoked by the memory manager, and contains parameters that are
very specific to the allocation of memory, the location of shared groups, and
the initialization of the virtual table. Think of it this way. A method is a
function that changes the state of the class. A constructor is a function that
converts <I>raw memory</I>, i.e., pure bits into an initialized class.
<P>In most texts the discussion of constructors deals largely with the end
result, and conventions adhered to within the language. The discussion usually
deals with the dos and don'ts of the language, its limitations, and workarounds
for when you really need to do what they just said was a no-no. However,
specifics pertaining to the actual implementation are only briefly mentioned.
The real problem comes when the discussion centers on constructors and multiple
inheritance. As we have seen in previous chapters, multiple inheritance
introduces some major complexities.
<P>Constructors are one of those things that appears simple on the surface, but
really isn't. As is the case with all other aspects, the problem grows in
complexity with the type of hierarchy. In the case of a single class with no
superclasses, the constructor is merely a call to one of the methods. In the
case of inheritance, a constructor would simply call the constructor for each of
its base classes before executing its own code. In the case of shared (virtual)
inheritance, the matter gets immediately worse. The issue comes up, how do we
insure that the constructor for each virtual base class gets called only once?
On top of that, how do we decide who gets to initialize the virtual base class?
<P>In SAL specifically, the process of construction is divided into two phases.
The objective in this design was to ensure that a constructor remains as close
in implementation to a normal method as possible. Thus, a pre-constructor phase
is completed before the constructor for the most-derived class in an instance is
called. This pre-constructor phase is performed by a procedure called a
meta-constructor.
<P>A meta-constructor's job is to format the data of the class in order to make
sure that things are <I>practically</I> initialized. When it is through, the
only thing un-initialized within an instance will be its members. A
meta-constructor performs a low-level initialization of the entire instance, and
then calls the constructor for the most-derived class. Each normal constructor
then calls the constructors for its base classes and then initializes the
members of its own class. In other words, a meta-constructor pre-initializes the
entire class, and then the normal constructors (each in their order) initialize
their specific class
<P>A meta-constructor has three main responsibilities. Each of these will be
explained in detail shortly.
<OL>
<LI>Set the init flag for virtual base classes. All virtual base classes are
preceeded by a byte that acts as a flag. Base class constructors must check
this byte to see if it is non-zero before calling the constructor of a virtual
base class. If it is zero, it will be set to one and the constructor will be
called. Otherwise the constructor for the virtual base class will not be
called.
<LI>Initialize vtable pointers for entire class. We know from the previous
chapter that not all classes within an instance have the same vtable pointer.
The easiest way to make this work is for the meta-constructor to perform the
task.
<LI>Call the meta-constructor for any nested classes. If a class has an
instance of another class as one of its members (i.e., not a pointer to the
class, but a live instance), then the meta-constructor for that member needs
to be called. The easiest way to ensure that this happens only once for a
containing class is for the meta-constructor to perform the task. <!-- <li>Call the specified constructor for the most derived
class. After all its work is through, the meta-constructor will then call the
constructor of the most derived class in the instance. This can be either the
default constructor or a constructor that the programmer has specified.</li>
--></LI></OL>The meta-constructor is purposefully complex so that the regular
class constructor, i.e., the one that the programmer writes may be as simple as
possible. A lot effort has gone into making the meta-constructor perform as much
low-level labor as as could be done. Such things might be left up to the
constructor, but then they would be agonizingly complex. The constructor has
just two remaining tasks.
<OL>
<LI>Call the constructor for each superclass. By default, this is done in the
order that the superclasses are listed in the declaration. However, this can
also be specified using the <TT>super</TT> keyword, a feature borrowed from
Java.
<LI>Initialize all members for for the specific instance of the class.
</LI></OL>By convention, the memory manager will invoke the constructor for the
most derived class. That constructor will then call the constructors for each of
its base classes before initializing the members of its own class. This
post-order traversal of the hierarchy ensures that base classes are initialized
prior to the derived classes. As a result, a subclass can always assume that its
base classes have been initialized and are in a valid state.
<P><!-------------------------------------------------------------------------------->
<H2>11.2 The meta-Constructor</H2><!-------------------------------------------------------------------------------->In
one aspect, the meta-constructor can be compared to the format utility in DOS,
which prepares a disk for storing information using FAT12 or FAT16 (or the newer
FAT32). The surface of the disk has to be initialized so that the sectors are
properly marked off. Then, the boot sector is written at sector 0, and finally a
blank root directory is written to a certain spot on the disk. This is probably
the closest thing to compare to a meta-constructor. It is a <I>low level</I>
constructor. At the beginning of this chapter, we talked about four tasks that a
meta-constructor has to perform. Let's go over these one by one.
<P>
<H3>11.2.1 Init Flag for Virtual Base Classes</H3><!-------------------------------------------------------------------------------->Let's
pull one of the examples out of Chapter 9. This is one of the most classic
examples for explaining inheritance because it involves all of its concepts.
Listing {SMI} shows the well-known multiple inheritance diamond. <PRE> type
A is class
x, y: int;
end class; // A
// / \
B is class // / \
extends shared A; // B C
// \ /
m, n: int // \ /
end class; // D
C is class
extends shared A;
p, q: int
end class;
D is class
extends B, C;
i, j: int
end class;
<B>Listing {SMI}.</B> Shared Multiple Inheritance
</PRE>As we have seen previously, the instance of A is placed into its own
group. In order to ensure that each group gets called once and only once, a byte
is placed in memory immediately prior to the group. The group delta can be
decremented by one in order to get this byte. If it is set, then the constructor
for the most derived class within that group has been called. Otherwise, the
byte should be set to one and the group's base constructor should be called.
Figure {SMIINIT} has been updated to show the presence of the init-byte for A's
group.
<P>
<MENU><IMG src="Chapter 11 Constructors and Destructors.files/SMIINIT.gif">
<P><FONT face=arial size=-1><B>Figure {SMIINIT}</B> Memory layout for an
instance of class A in listing {SMI}. Class D's group is immediately preceeded
by a byte indicating whether or not the group has been
initialized</FONT></P></MENU>As a comparison, C++ constructors only call the
constructors for base classes within their own group. C++ ensures that a shared
group is initiailzed only once by calling the constructor for its most derived
class first. In other words, in C++, all virtual base classes are initialized
before all non-virtual base classes. The constructors for virtual base classes
are called by the constructor of the most-derived class.
<P>
<H3>11.2.2 Virtual Table Pointer Initialization</H3><!-------------------------------------------------------------------------------->Since
the SAL compiler only performs one pass and has a limited look ahead, all
classes have a vtable pointer reguardless of whether or not they have virtual
functions. Theoretically, in a single inheritance system, vtable initialization
could take place by the last base class. If A inherits from B, which inherits
from C, then C would initialize the vtable pointer. This is easy to do, since
under single inheritance all classes share the same vtable pointer.
<P>This is complicated by multiple inheritance, where not all classes within an
instance share the same vtable. In SAL, a class's meta-constructor "knows" where
all of the instance's vtable pointers lie from information known at
compile-time-- as well as where they need to point to, and pokes them into
memory one by one.
<P>Evidentally implementations of C++ initialize their vtable pointers by having
them passed into each constructor along with the pointer to (the soon-to-be)
this. In other words, C++ constructors take two implicit parameters: one for
this, and one for the vtable pointer. Implementations of C++ do not split the
construction of their classes into two phases like SAL.
<P>
<H3>11.2.3 Initialize all Member Classes</H3><!-------------------------------------------------------------------------------->A
member class may be one of two things. It may be a member of a class that is an
instance of another class (i.e., not a pointer or a reference), or it may be a
class that has been declared within another class (a nested class). In SAL,
member classes are initialized by default constructor only. A class may not be
declared as a member of another class unless it has a default constructor or no
constructor at all. In the latter case, the compiler may generate a constructor.
<P>It is important to remember that in SAL all member classes are initialized
before any parts of the containing class are initialized. This can be done,
since member classes are largely independant of the containing class, and are
completely unaware of it. The order in which member classes is declared is
unimportant, and is undefined in SAL.
<P><!---
<h3>11.2.4 Call the Constructor of the Most-Derived Class</h3>
The one remaining task to be accomplished is for the meta-constructor to invoke
the normal constructor for the instance's most derived class. The two-phase
construction of classes in SAL enables class constructors to behave more closely
like functions. This is not so much for the benefit of the programmer, rather it
is a simplification that makes the SAL compiler easier to write- -a definate bonus
to the student studying this text. In either case, be it SAL or C++, there are
two aspects to class construction, a low level aspect and a high level aspect.
Where the responsibility of each of these lies is a job for the people that
implement the compiler.<p>
At this time, we should reiterate that the job of a meta-constructor is to
low-level initialize the entire class. The job of each normal constructor is to
high-level initialize their specific instance. More than likely, C++ constructors
get a flag saying whether or not their class is most derived. If the flag is
true, then they perform this low-level task. The only problem with this approach
is the duplicated code for each constructor in a class. If a given class had
three constructors, then all three would contain the low-level pre-initialization
code. For this reason and the reasons of simplicity already stated, SAL places
the task of low-level pre-initialization of an entire instance into a separate
procedure: the meta-constructor.<p>
--><!-------------------------------------------------------------------------------->
<H2>11.3 Class Constructors</H2><!-------------------------------------------------------------------------------->Ideally,
the normal constructor for a class initializes the members of its specific
instance, a specific instance being that portion of a class instance that does
not include the inherited portion(s). In SAL, the constructors of a class are
fundamentally the same as all other methods. There are some specific exceptions
to this rule, but on the whole it applies. However, there is a lengthy list of
things to consider.
<P>First of all, if a class has more than one constructor, it should not call
another one of its constructors. The result in this case is undefined. Some
languages like Java allow one constructor to call another constructor. This is
usually the result of a design where the programmer wants to place common
initialization code in one place. That way, changes to the way a class ought to
be initialized need only be made in one place. The best work-around for this is
to have a separate initialization method that all constructors can call. This is
common advice in C++. It is also common advice to make this separate
<TT>init()</TT> method private. SAL, however, does not have public and private
methods and members.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -