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

📄 chapter 11 constructors and destructors.htm

📁 英文版编译器设计:里面详细介绍啦C编译器的设计
💻 HTM
📖 第 1 页 / 共 5 页
字号:
<P>Another consideration is that there is no such thing as a virtual 
constructor. Consider a case where we manage a list of objects: triangles, 
circles and squares. Each of these objects inherits from a common base class 
called shape. At times we may want to create a new instance of a particular 
object, but the problem is that we only know about one class--the shape. At 
times we may want to create an instance like the one that we are currently 
working with, but the problem is that we do not know anything about the deriving 
classes. In this case, what is needed is a virtual constructor.
<P>Virtual constructors are nice in theory, but their implementation is quite 
impractical. How can you build something that you don't know anything about? 
Developers work around this limitation in one of two ways. One way is for the 
common base class to have an ID member that all deriving classes set. Whenever a 
base class wants to make another instance like the one they've got, it uses a 
switch statement. The problem here is that every time a new class derives from 
the common base class, the base class needs to be updated. A better way is for 
the base class to have a virtual method called <TT>copy()</TT> that all deriving 
classes can override. The triangle, circle, and square classes then have their 
own implementation of copy that calls the new operator.
<P>A third consideration is that virtual function calls are not used inside a 
constructor. Imagine a base class having any number of virtual functions that 
the constructor has to invoke. Since a virtual function call may cause 
modification to a portion of the class that has not yet been initialized, the 
compiler will use the local versions of these functions only. As a general rule, 
programmers should avoid calling methods that are virtual from within the 
constructor. SAL has no real way of protecting against this sort of thing. In 
C++, the compiler will make sure that functions are never invoked virtually. 
This is especially important in C++, since the vtable might not even be 
initialized yet.
<P>A fourth no-no is to never try to take the address of a constructor. 
Programmers try to do this thinking that they can have a general function 
pointer that they can call that makes a new whatever-it-is. The problem is that 
the constructor does not allocate memory--it only initializes it. Allocation is 
performed by the <TT>new</TT> and <TT>delete</TT> operators. In order to work 
around this, a programmer should use a wrapper function for each class that they 
want to use in this way. A general function pointer called <TT>MakeNew</TT> 
could then point to a <TT>NewTriangle</TT> function that made a new 
<TT>triangle</TT>, a <TT>NewCircle</TT> function that made a new 
<TT>circle</TT>, and a <TT>NewSquare</TT> function that made a new 
<TT>square</TT>.
<P>Another thing to consider is that constructors are not inherited. Suppose 
there are two classes, A and B. A inherits B. A has a default constructor and B 
does not. If constructors were inherited, then making a new instance of class A 
would only initialize B. In practice, constructors only initialize the classes 
to which they immediately belong, and recursively call the constructors for the 
base classes that are inherited.
<P>In spite of this rule, some similar feature is still necessary. In order to 
work around this rule, the compiler will generate a default constructor for 
every class that needs one. The default constructor is generated for a deriving 
class if and only if a base class has a constructor and the deriving class does 
not. In the case of a member classes (i.e., nested classes), their 
initialization is managed by the meta-constructor. C++ also has copy 
constructors and overloaded assignment operators, which are treated in much the 
same way as default constructors. SAL does not have these features, and so is 
rather incomplete.
<P>A final consideration is that constructors do not return a value. Programmers 
often want to return a success or failure result from a constructor. 
Unfortunately, due to syntax this is not possible. In SAL a constructor is 
invoked as a parameter to the standard function <TT>new()</TT>, like so: <PRE>      pTriangle:= new(triangle);
</PRE>There is nowhere for the constructor for <TT>triangle</TT> to return its 
result. Other languages like C++ and Java have this same problem, and typically 
programmers work around it by throwing an exception whenever creation fails. 
Usually if allocation fails <TT>new()</TT> will return a <TT>null</TT>, and the 
constructor will never have been invoked. Usually a programmer will want 
creation to fail if the constructor allocates some resource, and that allocation 
fails for some reason. In this case an exception would be appropriate, however, 
caution should be taken to ensure that global instances of such a class not be 
made.
<P>This covers most of the caveats concerning constructors. The list is rather 
long and complex, but is fairly important to know when designing an OOP 
compiler.
<P>
<H3>11.3.1 Constructors in SAL</H3><!-------------------------------------------------------------------------------->In 
order to keep the SAL symbol table simple, there is no overloading of any sort. 
There is no function overloading, no operator overloading, and there is no 
constructor overloading. This meant that when SAL was designed, its designers 
were faced with the problem that each class could have only one constructor. In 
order to overcome this, they introduced named constructors
<P>To put it simply, having named constructors means that constructors do not 
have to have the same name as their class, like in C++ or in Java. The compiler 
knows that it is dealing with a constructor because of the presence of a 
keyword. In SAL, the default constructor has the same name as the class and no 
parameter list. All other constructors must have a unique name, and can have any 
parameter list.
<P>In most respects, constructors in SAL look like normal procedures. Here is a 
constructor for a class named <TT>Complex</TT>, that handels complex numbers. <PRE>      constructor Complex();
      begin
        r:= 0.0;
        i:= 0.0;
      end constructor;
</PRE>This constructor bears the same name as the class, and so it is the 
default constructor. It takes no parameters, and is therefore legal. We could 
make another constructor that sets the values of the <TT>Complex</TT> class. <PRE>      constructor SetRI(rr, ii: real);
      begin
        r:= rr;
        i:= ii;
      end constructor;
</PRE>Because of the syntax of SAL and the limited nature of the compiler, 
constructors other than the default constructor for a class can only be used in 
conjunction with the <TT>new()</TT> standard function. There is no way to 
declare variables that are instances of a class, and have them call a named 
constructor. This is not legal: <PRE>      var
        m: Complex.SetRI(1.0, 1.0);
</PRE>It can only be done this way: <PRE>      var
        m: ^Complex;
      
      begin
        m:= new(Complex::SetRI(1.0, 1.0));

        ...
</PRE>In order to declare a variable that is a class, the class must have either 
a default constructor or no constructors at all. This is also true for classes 
nested in records, and arrays of classes. We can make a local or global variable 
of type <TT>Complex</TT> since it has a default constructor. If <TT>Complex</TT> 
were to have a named constructor and no default constructor (even if the named 
constructor had no parameters), we could only declare a pointer to it and 
allocate instances from the heap.
<P>The default constructor may be generated by the compiler, but that will only 
happen when the class has no default constructor of its own, and it inherits 
from one or more classes each of which has its own default constructor. In other 
words, if all base classes have a default constructor and the deriving class 
does not, then the compiler will make a default constructor for the deriving 
class.
<P>On the other hand, the compiler will generate an error if a class has no 
constructors and it inherits from any class that has one or more named 
constructors but no default constructor. In this case, the compiler cannot 
generate a default constructor for the deriving class, since it could not know 
which of the named constructors to use nor would it know what parameters to use. 
If a class has a constructor, then it in effect saying "I need to be initialized 
in a specail way". If a class has a constructor, it must be called reguardless 
of how deeply nested it may be in a heirarchy. In short, if any of a deriving 
class's base classes has a constructor, then the deriving class <I>must</I> have 
a constructor of some sort. Additionally, a compiler-generated constructor can 
only call default constructors.
<P>
<H3>11.3.2 Constructor Calling Order</H3><!-------------------------------------------------------------------------------->Since 
the meta-constructor's job is to only call the constructor of the most-derived 
instance in a class, then it is important for each constructor to make sure that 
it calls the constructors for its base classes. By default, base classes are 
initialized in the order that they are declared. For instance, in the following 
declaration, <PRE>      class A                    \\  B     C
        extends B, C;            \\   \   /
                                 \\    \ /
        ...                      \\     A
      end class;
</PRE>B's constructor will be called first, then C's. Also, a class cannot call 
the constructor for a class unless it inherits from that class directly. In a 
heirarchy like this, <PRE>      C
      |
      |
      B
      |
      |
      A
</PRE>Class A cannot call any of C's constructors. It can only call B's. Classes 
can only call constructors for base classes that they directly inherit.
<P>Base class constructors are called through the <TT>super</TT> keyword. 
Consider a simple hierarchy. <PRE>      class C
        constructor C();
        begin
          ...
        end constructor;
      end class;

      class B
        constructor B();
        begin
          ...
        end constructor;
      end class;

      class A                    \\  B     C
        extends B, C;            \\   \   /
                                 \\    \ /
        constructor A();         \\     A
        begin
          super B;
          super C;
          ...
        end constructor;
      end class;
</PRE>The <TT>super</TT> keyword only calls the constructor for one class. In 
other words, it cannot have multiple constructor calls separated by commas, like 
some other keywords in SAL. For <TT>super</TT> to call a named constructor, 
scope resolution can be used. <PRE>      super ClassX::fooCtor(a, b);
</PRE>
<P>The <TT>super</TT> keyword in a constructor must come before any other 
executable statements within its body. This is illegal. <PRE>      constructor MakeIt(a, b: int);
      begin
        member1:= a;
        member2:= b;

        super FooClass;
      end constructor;
</PRE>It is not possible to initialize a derived class before its base classes. 
It is possible however for a named constructor to pass its parameters onto other 
named constructors. <PRE>      constructor MakeIt(a, b: int);
      begin
        super FooClass::MakeIt(a, b);

        ...

      end constructor;
</PRE>The <TT>super</TT> keyword is used in two situations. First, it is used 
when a deriving class wants to specify the calling order for its base class 
constructors. Second, it is used when a deriving class wants to call a named 
constructor. The compiler keeps track of which base classes have been 
initialized, and calls the default constructor for any remaing base classes 
(assuming the base class has a default constructor). If a base class has named 
constructors only, and the deriving class fails to call one of its constructors, 
then the compiler will give an error.
<P>The compiler ensures that all base classes are initialized by calling the 
default constructors for all remaining ones. It does this through the token 
queue, by pushing the appropriate calls in the order that they are declared.
<P><!-------------------------------------------------------------------------------->
<H2>11.4 Arrays and Records Containing Classes</H2><!-------------------------------------------------------------------------------->An 
additional feature that the meta-constructor approach gives us is the ability to 
construct classes that are elements of arrays, or fields of classes. This 
generalizes the meta-constructor concept from a low-level class initializer to a 
general, all purpose low-level initializer for any structured object.
<P>A meta-constructor is built for any structured data type that either requires 
initialization, or contains a nested structured data type that requires 
initialization. For instance, a class with a constructor requires 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -