📄 chapter 11 constructors and destructors.htm
字号:
<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 + -