⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 chapter 11 constructors and destructors.htm

📁 英文版编译器设计:里面详细介绍啦C编译器的设计
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<!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 + -