📄 oop.htm
字号:
in the example of the previous chapter. StrongForth's interpreter and compiler
have no difficulty at all to always select the correct version, because the
data type of the structure or the class of the object they are applied to is
known at compile time. The same holds true for overloading data members and
member words of different classes.</p>
<p>The three member words within the class definition are ordinary colon
definitions. The fact that their last input parameter is always an object of the
class makes them member words. To make the object available throughout the
definitions, it is generally assigned to a local named <kbd>THIS</kbd>. Exactly
like structures, objects can be allocated with <kbd>NEW</kbd> and deallocated with
<kbd>DELETE</kbd>:</p>
<pre><u>NEW POINT CONSTANT P1</u> OK
<u>P1 GET-POINT . .</u> 0 0 OK
<u>+5 -7 P1 SET-POINT</u> OK
<u>P1 GET-POINT . .</u> -7 5 OK
<u>P1 DELETE</u> OK</pre>
<p>Wait a moment. Why does the first <kbd>GET-POINT</kbd> return zero for both
data members? They are supposed <em>not</em> to be automatically initialized!
That's true. The initialization happens program controlled by implicitly executing
the third member word <kbd>POINT</kbd>. <kbd>NEW</kbd> does not only allocate the
data members of an object; it also evaluates a word with the same name as the
class. This word is called a <em>constructor</em> of the class, and it usually
contains code that initializes the data members. It is possible to provide multiple
overloaded constructors with different sets of parameters, for example:</p>
<pre>: POINT ( SIGNED SIGNED POINT -- 3RD )
LOCALS| THIS | THIS SET-POINT THIS ;</pre>
<p>Now, two different kinds of initializations are possible:</p>
<pre><u>NEW POINT DUP GET-POINT . . DELETE</u> 0 0 OK
<u>+12 +40 NEW POINT DUP GET-POINT . . DELETE</u> 40 12 OK</pre>
<p>A constructor always has exactly one output parameter, which is an unchanged
copy of the object that is provided as the last input parameter.
Note that <kbd>NEW</kbd> requires the existence of a constructor.
Without a constructor, it is not possible to create an object of a class.</p>
<p>The size of an object in address units can be determined from its class
with <kbd>SIZE-OBJECT</kbd>:<p>
<pre><u>DT POINT SIZE-OBJECT .</u> 12 OK</pre>
<p>12 address units? Since <kbd>POINT</kbd> has only two single cell members and one
cell occupies 4 address units, shouldn't it rather be 8? Objects actually contain
an additional cell for a pointer to the so-called <em>virtual member table</em>,
which contains runtime type information of the object. This will be elaborated in detail
in the section about virtual members. However, since the virtual member table also
contains the size of objects of the class, it is possible to determine the object size
not only from its class, but also from the object itself. This is not possible for
structures, because structures do not have virtual member tables:</p>
<pre><u>NEW POINT DUP SIZE . DELETE</u> 2 OK</pre>
<p><kbd>SIZE</kbd> for items of data type <kbd>OBJECT</kbd> returns the size
<em>in cells</em> of the object's data members. Based on <kbd>SIZE</kbd>, two words
have been defined that can be applied to all objects:</p>
<pre>: COPY ( OBJECT 1ST -- )
OVER SIZE OVER SIZE MIN
ROT CAST ADDRESS -> SINGLE 1+ ROT CAST ADDRESS -> SINGLE 1+
ROT MOVE ;
: ERASE ( OBJECT -- )
DUP CAST ADDRESS -> SINGLE 1+ SWAP SIZE ERASE ;</pre>
<p><kbd>COPY</kbd> copies the data members of an object to the data members of another
object of the same class. This is what is called in C++
<em>memberwise initialization</em>. However, note that something like
<em>default memberwise initialization</em> does not exist in StrongForth. All
initialization has to be done explicitly. For specific objects, <kbd>COPY</kbd>
can be overloaded if memberwise initialization is not desired. Since in most cases
these overloaded versions require direct access to the data members, they usually
have to be made member words.</p>
<p><kbd>ERASE</kbd> initializes all data members of an object with zero. Using
<kbd>ERASE</kbd>, the first constructor of the <kbd>POINT</kbd> class can be
simplified:</p>
<pre>: POINT ( POINT -- 1ST )
DUP ERASE ;</pre>
<p>The definitions of <kbd>COPY</kbd> and <kbd>ERASE</kbd> reveal an implementation
detail of objects. The phrase <kbd>CAST ADDRESS -> SINGLE 1+</kbd> shows that an
item of data type <kbd>OBJECT</kbd> is nothing else but a pointer to an
address one cell below the first data member. The cell it actually points to contains
the pointer to the virtual member table:</p>
<table border=1 cellpadding=3 width=25%>
<tr>
<td align=center>virtual table pointer</td>
</tr>
<tr>
<td align=center>PX</td>
</tr>
<tr>
<td align=center>PY</td>
</tr>
</table>
<h2><kbd>THIS</kbd> Object</h2>
<p>All data members and member words have a specific object of their class as the
last input parameter. As the definition of the <kbd>POINT</kbd> class demonstrates,
it is often convenient to have this object available as a local within the definition
of a member word. By convention, this local is called <kbd>THIS</kbd>, and creating
this local is usually the first action of member words. The phrase
<kbd>LOCALS| THIS |</kbd> can be replaced by the immediate word <kbd>>THIS</kbd>,
which does exactly the same thing. Since the ANS Forth host system does not generally
permit multiple occurences of <kbd>LOCALS| ... |</kbd>, using
<kbd>>THIS</kbd> and <kbd>LOCALS|</kbd> within the definition of the same member word
might cause an exception being thrown. Instead of</p>
<pre>: COPY ( OBJECT 1ST -- )
>THIS LOCALS| FROM |
FROM CAST ADDRESS -> SINGLE 1+
THIS CAST ADDRESS -> SINGLE 1+
FROM SIZE THIS SIZE MIN MOVE ;</pre>
<p>you should rather write</p>
<pre>: COPY ( OBJECT 1ST -- )
LOCALS| THIS FROM |
FROM CAST ADDRESS -> SINGLE 1+
THIS CAST ADDRESS -> SINGLE 1+
FROM SIZE THIS SIZE MIN MOVE ;</pre>
<p>In the member word definitions of the <kbd>POINT</kbd> class, <kbd>THIS</kbd>
is used quite often. Whenever a data member or another member word is used, it
has to be preceeded by a reference to the <kbd>THIS</kbd> object. Wouldn't it be
nice if the compiler automatically inserted <kbd>THIS</kbd> whenever a data member
or a member word of the same class is used? Of course, it should only do so if an
object of the class type is not already on the stack, because in some cases the
data member or the member word belongs to a different object of the class type or
even to an object of a different class. Such a feature really exists! It is actually
the default for all data members defined with <kbd>MEMBER</kbd>, <kbd>CMEMBER</kbd>,
etc. Member words that are compiled with <kbd>:MEMBER</kbd> instead of <kbd>:</kbd>
also compile an implicit <kbd>THIS</kbd> if an object of the <kbd>THIS</kbd> class
is not already on the data stack. The definition of the <kbd>POINT</kbd> class can
now be written shorter:</p>
<pre>DT OBJECT PROCREATES POINT
CLASS POINT
BODY
+0 MEMBER PX
+0 MEMBER PY
:MEMBER SET-POINT ( SIGNED SIGNED POINT -- )
>THIS PY ! PX ! ;
:MEMBER GET-POINT ( POINT -- SIGNED SIGNED )
>THIS PX @ PY @ ;
:MEMBER POINT ( POINT -- 1ST )
>THIS +0 +0 SET-POINT THIS ;
ENDCLASS</pre>
<p>The <em>Automatic <kbd>THIS</kbd></em> feature works only within class definitions,
because that's where almost all usages of data members and own member words refer to
<kbd>THIS</kbd>. It is important to understand how this feature works, because
it can lead to ambiguities if not used with care. Let's investigate what happens once
the compiler parses <kbd>PY</kbd> in the definition of <kbd>SET-POINT</kbd>. At this
point, the compiler data type heap consists of two times data type <kbd>SIGNED</kbd>.
The attempt to find <kbd>PY</kbd> in the dictionary fails, because <kbd>PY</kbd>
has an object of data type <kbd>POINT</kbd> as its only input parameter. But the
search continues. <kbd>CLASS</kbd> actually appends a special word list called
<kbd>AUTOTHIS</kbd> to the <em>end</em> of the search order. This word list is
only searched when the search in all other word lists of the search order failed.
<kbd>AUTOTHIS</kbd> contains an immediate word <kbd>PY</kbd> with no parameters,
that was created by the previous definition of <kbd>PY</kbd> with <kbd>MEMBER</kbd>.
This immediate word temporarily removes the <kbd>AUTOTHIS</kbd> word list from the
search order, then evaluates <kbd>THIS PY</kbd>, and finally restores the search
order. Temporarily removing the <kbd>AUTOTHIS</kbd> word list from the search
order is necessary in order to avoid recursive executions of the immediate word if
inserting <kbd>THIS</kbd> doesn't help. But in this case, evaluating <kbd>THIS</kbd>
results in <kbd>SIGNED SIGNED POINT</kbd> on the compiler data type heap, and
the subsequent search of <kbd>PY</kbd> is successful. The same thing happens with
<kbd>PX</kbd> in the definition of <kbd>SET-POINT</kbd>.</p>
<p><kbd>:MEMBER</kbd> and all defining words for data members actually create two
definitions: the proper definition in the current compilation word list, and an
immediate word with the same name in the <kbd>AUTOTHIS</kbd> word list. Note that
the <em>Automatic <kbd>THIS</kbd></em> feature works only after a local with the name
<kbd>THIS</kbd> has been defined.</p>
<p>The defining word that defines the immediate word is called <kbd>AUTOTHIS</kbd>,
just like the word list.
It parses the input source for the name of the word to be defined, and then skips back
in the input source specification, so that the name can be parsed once more. With
<kbd>AUTOTHIS</kbd>, The definition of <kbd>:MEMBER</kbd> becomes pretty simple:</p>
<pre>: :MEMBER ( OBJ-SIZE -- 1ST COLON-DEFINITION )
AUTOTHIS : ;</pre>
<p>The two words that add and remove the <kbd>AUTOTHIS</kbd> word list to and from the
search order might be useful at other places, because they work with other word lists
as well:</p>
<pre><u>WORDS APPEND-WORDLIST</u>
APPEND-WORDLIST ( WID -- ) OK
<u>WORDS STRIP-WORDLIST</u>
STRIP-WORDLIST ( -- ) OK
<u>ORDER</u>
CURRENT: FORTH
CONTEXT: FORTH OK
<u>ENVIRONMENT-WORDLIST APPEND-WORDLIST ORDER</u>
CURRENT: FORTH
CONTEXT: FORTH ENVIRONMENT OK
<u>STRIP-WORDLIST ORDER</u>
CURRENT: FORTH
CONTEXT: FORTH OK</pre>
<p>Note that the <em>Automatic <kbd>THIS</kbd></em> feature does not work with
<kbd>RECURSE</kbd>. The reason is that <kbd>RECURSE</kbd> does not actually perform
a dictionary search. It just tries to compile the current definition and expects
that its parameters are on the stack. Recursive member words generally require an
explicit <kbd>THIS</kbd> before the <kbd>RECURSE</kbd>.</p>
<h2>Encapsulation</h2>
<p>Encapsulation is one of the major properties of object oriented programming. It
means that classes hide the details of their internal data representation by just
providing access to a number of interface member words that restrict the access to
internal data. The internal representation of the <kbd>POINT</kbd> class is not
encapsulated, because you can freely access it's data members:</p>
<pre><u>NEW POINT CONSTANT POINT3</u> OK
<u>-20 POINT3 PX !</u> OK
<u>POINT3 GET-POINT . .</u> 0 -20 OK</pre>
<p>Like C++, StrongForth has three levels of information hiding:</p>
<ul>
<li><em>Private</em> members can be accessed only within the same class definition.
For example, if <kbd>PX</kbd> were a private data member, it could be used by
<kbd>SET-POINT</kbd>, <kbd>GET-POINT</kbd> and <kbd>POINT</kbd>, but not by any
word that is defined after <kbd>ENDCLASS</kbd>.</li>
<li><em>Protected</em> members can be accessed like private members, and
additionally within the class definitions of all derived classes.</li>
<li><em>Public</em> members don't have any access restrictions. So far, all members
of the <kbd>POINT</kbd> class are public.</li>
</ul>
<p>Note that the three levels can be applied to both data members and member words.
Usually, all data members are either private or protected, but it is often useful
also to restict access to member words that are supposed to be used only internally
to the class.</p>
<p>Access to data members and member words can be restricted by defining them in
the <kbd>PRIVATE</kbd> or <kbd>PROTECTED</kbd> word lists.
Each class has its own instances of these two word
lists, and it is generally not possible to access them from outside the class
definition. For example, we can make the data members of the <kbd>POINT</kbd> class
private:</p>
<pre>DT OBJECT PROCREATES POINT
CLASS POINT
BODY
ALSO PRIVATE DEFINITIONS
+0 MEMBER PX
+0 MEMBER PY
ALSO FORTH DEFINITIONS PREVIOUS
:MEMBER SET-POINT ( SIGNED SIGNED POINT -- )
>THIS PY ! PX ! ;
:MEMBER GET-POINT ( POINT -- SIGNED SIGNED )
>THIS PX @ PY @ ;
:MEMBER POINT ( POINT -- 1ST )
>THIS +0 +0 SET-POINT THIS ;
ENDCLASS</pre>
<p>To be able to access the two data members within the class definition, the
<kbd>PRIVATE</kbd> word list has to be in the search order. <kbd>CLASS</kbd>
saves the search order and the current compilation word list at the beginning
of the class definition, and <kbd>ENDCLASS</kbd> restores both.
After <kbd>ENDCLASS</kbd>, the <kbd>PRIVATE</kbd> word list of the
<kbd>POINT</kbd> class is inaccessible, which means that all access to the
data members is restricted to using the public member words:</p>
<pre><u>NEW POINT CONSTANT POINT4</u> OK
<u>+20 POINT4 PX !</u>
+20 POINT4 PX ? undefined word
SIGNED POINT
<u>POINT4 GET-POINT . .</u> 0 0 OK
<u>+20 +0 POINT4 SET-POINT</u> OK
<u>POINT4 GET-POINT . .</u> 0 20 OK</pre>
<p>In some cases, it is necessary for one class to access the data members of
another class. Does this mean you have to make the data members public? No,
not necessarily. StrongForth supports the same mechanism as C++. A class that
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -