As well as defining slot names and types, a class can also associate an
\emph{initial value} with each slot defined by itself or one of its
subclasses. A class $C$ provides an \emph{initialization function} (see
-\xref{sec:concepts.classes.c}, and \xref{sec:structures.root.sodclass}) which
-sets the slots of a \emph{direct} instance of the class to the correct
+\xref{sec:concepts.lifecycle.birth}, and \xref{sec:structures.root.sodclass})
+which sets the slots of a \emph{direct} instance of the class to the correct
initial values. If several of $C$'s superclasses define initializers for the
same slot then the initializer from the most specific such class is used. If
none of $C$'s superclasses define an initializer for some slot then that slot
doesn't define any messages, so it doesn't have any methods. In Sod, a class
slot containing a function pointer is not at all the same thing as a method.)
-\subsubsection{Instance allocation, imprinting, and initialization}
-It is in general not sufficient to declare (or @|malloc|) an object of the
-appropriate class type and fill it in, since the class type only describes an
-instance's layout from the point of view of a single superclass chain. The
-correct type to allocate, to store a direct instance of some class is a
-structure whose tag is the class name suffixed with `@|__ilayout|'; e.g., the
-correct layout structure for a direct instance of @|MyClass| would be
-@|struct MyClass__ilayout|.
-
-Instance layouts may be declared as objects with automatic storage duration
-(colloquially, `allocated on the stack') or allocated dynamically, e.g.,
-using @|malloc|. Sod's runtime system doesn't retain addresses of instances,
-so, for example, Sod doesn't make using a fancy allocator which sometimes
-moves objects around in memory any more difficult than it needs to be.
-
-Once storage for an instance has been allocated, it must be \emph{imprinted}
-before it can be used. Imprinting an instance stores some metadata about its
-direct class in the instance structure, so that the rest of the program (and
-Sod's runtime library) can tell what sort of object it is, and how to use
-it.\footnote{%
- Specifically, imprinting an instance's storage involves storing the
- appropriate vtable pointers in the right places in it.} %
-A class object's @|imprint| slot points to a function which will correctly
-imprint storage for one of that class's instances.
-
-Once an instance's storage has been imprinted, it is possible to send the
-instance messages; however, the instance's slots are uninitialized at this
-point, so most methods are unlikely to do much of any use. So, usually, you
-don't just want to imprint instance storage, but to \emph{initialize} an
-instance. Initialization includes imprinting, but also sets the new
-instance's slots to their initial values, as defined by the class. If
-neither the class nor any of its superclasses defines an initializer for a
-slot then it will not be initialized.
-
-There is currently no facility for providing parameters to the instance
-initialization process (e.g., for use by slot initializer expressions).
-Instance initialization is a complicated matter and for now I want to
-experiment with various approaches before committing to one. My current
-interim approach is to specify slot initializers where appropriate and send
-class-specific messages for more complicated parametrized initialization.
-
-Automatic-duration instances can be conveniently constructed and initialized
-using the \descref{SOD_DECL}[macro]{mac}. No special support is currently
-provided for dynamically allocated instances. A simple function using
-@|malloc| might work as follows.
-\begin{prog}
- void *new_instance(const SodClass *c) \\
- \{ \\ \ind
- void *p = malloc(c@->cls.initsz); \\
- if (!p) return (0); \\
- c@->cls.init(p); \\
- return (p); \- \\
- \}
-\end{prog}
-
-\subsubsection{Instance finalization and deallocation}
-There is currently no provided assistance for finalization or deallocation.
-It is the programmer's responsibility to decide and implement an appropriate
-protocol. Note that to free an instance allocated from the heap, one must
-correctly find its base address: the \descref{SOD_INSTBASE}[macro]{mac} will
-do this for you.
-
-The following simple mixin class is suggested.
-\begin{prog}
- [nick = disposable] \\
- class DisposableObject : SodObject \{ \\- \ind
- void release() \{ ; \} \\
- \quad /* Release resources held by the receiver. */ \- \\-
- \}
- \\+
- code c : user \{ \\- \ind
- /\=\+* Free object p's instance storage. If p is a DisposableObject \\
- {}* then release its resources beforehand. \\
- {}*/ \- \\
- void free_instance(void *p) \\
- \{ \\ \ind
- DisposableObject *d = SOD_CONVERT(DisposableObject, p); \\
- if (d) DisposableObject_release(d); \\
- free(d); \- \\
- \} \- \\
- \}
-\end{prog}
-
\subsubsection{Conversions}
Suppose one has a value of type pointer to class type of some class~$C$, and
wants to convert it to a pointer to class type of some other class~$B$.
from the direct method definition if there was one, or an unspecified value
otherwise.
+%%%--------------------------------------------------------------------------
+\section{The object lifecycle} \label{sec:concepts.lifecycle}
+
+\subsection{Creation} \label{sec:concepts.lifecycle.birth}
+
+Construction of a new instance of a class involves three steps.
+\begin{enumerate}
+\item \emph{Allocation} arranges for there to be storage space for the
+ instance's slots and associated metadata.
+\item \emph{Imprinting} fills in the instance's metadata, associating the
+ instance with its class.
+\item \emph{Initialization} stores appropriate initial values in the
+ instance's slots, and maybe links it into any external data structures as
+ necessary.
+\end{enumerate}
+The \descref{SOD_DECL}[macro]{mac} handles constructing instances with
+automatic storage duration (`on the stack'). Currently, there is no built-in
+support for constructing dynamically-allocated instances.
+
+\subsubsection{Allocation}
+Instances of most classes (specifically including those classes defined by
+Sod itself) can be held in any storage of sufficient size. The in-memory
+layout of an instance of some class~$C$ is described by the type @|struct
+$C$__ilayout|, and if the relevant class is known at compile time then the
+best way to discover the layout size is with the @|sizeof| operator. Failing
+that, the size required to hold an instance of $C$ is available in a slot in
+$C$'s class object, as @|$C$__class@->cls.initsz|.
+
+It is not in general sufficient to declare, or otherwise allocate, an object
+of the class type $C$. The class type only describes a single chain of the
+object's layout. It is nearly always an error to use the class type as if it
+is a \emph{complete type}, e.g., to declare objects or arrays of the class
+type, or to enquire about its size or alignment requirements.
+
+Instance layouts may be declared as objects with automatic storage duration
+(colloquially, `allocated on the stack') or allocated dynamically, e.g.,
+using @|malloc|. They may be included as members of structures or unions, or
+elements of arrays. Sod's runtime system doesn't retain addresses of
+instances, so, for example, Sod doesn't make using fancy allocators which
+sometimes move objects around in memory any more difficult than it needs to
+be.
+
+There isn't any way to discover the alignment required for a particular
+class's instances at runtime; it's best to be conservative and assume that
+the platform's strictest alignment requirement applies.
+
+The following simple function correctly allocates and returns space for an
+instance of a class given a pointer to its class object @<cls>.
+\begin{prog}
+ void *allocate_instance(const SodClass *cls) \\ \ind
+ \{ return malloc(cls@->cls.initsz); \}
+\end{prog}
+
+\subsubsection{Imprinting}
+Once storage has been allocated, it must be \emph{imprinted} before it can be
+used as an instance of a class, e.g., before any messages can be sent to it.
+
+Imprinting an instance stores some metadata about its direct class in the
+instance structure, so that the rest of the program (and Sod's runtime
+library) can tell what sort of object it is, and how to use it.\footnote{%
+ Specifically, imprinting an instance's storage involves storing the
+ appropriate vtable pointers in the right places in it.} %
+A class object's @|imprint| slot points to a function which will correctly
+imprint storage for one of that class's instances.
+
+Once an instance's storage has been imprinted, it is technically possible to
+send messages to the instance; however the instance's slots are still
+uninitialized at this point, the applicable methods are unlikely to do much
+of any use unless they've been written specifically for the purpose.
+
+The following simple function imprints storage at address @<p> as an instance
+of a class, given a pointer to its class object @<cls>.
+\begin{prog}
+ void imprint_instance(const SodClass *cls, void *p) \\ \ind
+ \{ cls@->cls.imprint(p); \}
+\end{prog}
+
+\subsubsection{Initialization}
+The final step for constructing a new instance is to \emph{initialize} it, to
+establish the necessary invariants for the instance itself and the
+environment in which it operates.
+
+Details of initialization are necessarily class-specific, but typically it
+involves setting the instance's slots to appropriate values, and possibly
+linking it into some larger data structure to keep track of it.
+
+Classes can declare initial values for their slots. A class object's @|init|
+slot points to a function which will establish the appropriate initial values
+for a new instance's slots. Slots are not initialized in any particularly
+useful order. The @|init| function also imprints the instance storage.
+
+The provided initialization protocol is extremely simplistic; most notably,
+it's not possible to pass parameters into the initialization process.
+Classes which have more complex requirements will need to define and
+implement their own additional (or alternative) protocols.
+
+\subsubsection{Example}
+The following is a simple function, with syntactic-sugar macro, which
+allocate storage for an instance of a class, imprints and initializes it, and
+returns a pointer to the new instance.
+\begin{prog}
+ void *make_instance(const SodClass *c) \\
+ \{ \\ \ind
+ void *p = malloc(c@->cls.initsz); \\
+ if (!p) return (0); \\
+ c@->cls.init(p); \\
+ return (p); \- \\
+ \}
+ \\+
+ \#define MAKE(cls) (cls *)make_instance(cls\#\#__class)
+\end{prog}
+
+
+\subsection{Destruction}
+\label{sec:concepts.lifecycle.death}
+
+Destruction of an instance, when it is no longer required, consists of two
+steps.
+\begin{enumerate}
+\item \emph{Teardown} releases any resources held by the instance and
+ disentangles it from any external data structures.
+\item \emph{Deallocation} releases the memory used to store the instance so
+ that it can be reused.
+\end{enumerate}
+
+\subsubsection{Teardown}
+Details of teardown are class-specific, but typically it involves releasing
+resources held by the instance, and possibly unlinking it from some larger
+data structure which used to keep track of it.
+
+There is no provided protocol for teardown: classes whose instances require
+teardown behaviour must define and implement an appropriate protocol of their
+own. The following class may serve for simple cases.
+\begin{prog}
+ [nick = disposable] \\
+ class DisposableObject : SodObject \{ \\- \ind
+ void release() \{ ; \} \\
+ \quad /* Release resources held by the receiver. */ \- \\-
+ \}
+ \\+
+ code c : user \{ \\- \ind
+ /* If p is a a DisposableObject then release its resources. */ \\
+ void maybe_dispose(void *p) \\
+ \{ \\ \ind
+ DisposableObject *d = SOD_CONVERT(DisposableObject, p); \\
+ if (d) DisposableObject_release(d); \- \\
+ \} \- \\
+ \}
+\end{prog}
+
+\subsubsection{Deallocation}
+The details of instance deallocation are obviously specific to the allocation
+strategy used by the instance, and this is often orthogonal from the object's
+class.
+
+The code which makes the decision to destroy an object may often not be aware
+of the object's direct class. Low-level details of deallocation often
+require the proper base address of the instance's storage, which can be
+determined using the \descref{SOD_INSTBASE}[macro]{mac}.
+
+\subsubsection{Example}
+The following is a counterpart to the @|new_instance| function
+(\xref{sec:concepts.lifecycle.birth}), which tears down and deallocates an
+instance allocated using @|malloc|.
+\begin{prog}
+ void free_instance(void *p) \\
+ \{ \\ \ind
+ SodObject *obj = p; \\
+ maybe_dispose(p); \\
+ free(SOD_INSTBASE(obj)); \- \\
+ \}
+\end{prog}
+
%%%--------------------------------------------------------------------------
\section{Metaclasses} \label{sec:concepts.metaclasses}