Slots and Inheritance
As I discussed in the previous chapter, classes inherit behavior from their superclasses thanks to the generic function machinery—a method specialized on class A
is applicable not only to direct instances of A
but also to instances of A
‘s subclasses. Classes also inherit slots from their superclasses, but the mechanism is slightly different.
In Common Lisp a given object can have only one slot with a particular name. However, it’s possible that more than one class in the inheritance hierarchy of a given class will specify a slot with a particular name. This can happen either because a subclass includes a slot specifier with the same name as a slot specified in a superclass or because multiple superclasses specify slots with the same name.
Common Lisp resolves these situations by merging all the specifiers with the same name from the new class and all its superclasses to create a single specifier for each unique slot name. When merging specifiers, different slot options are treated differently. For instance, since a slot can have only a single default value, if multiple classes specify an :initform
, the new class uses the one from the most specific class. This allows a subclass to specify a different default value than the one it would otherwise inherit.
On the other hand, :initarg
s needn’t be exclusive—each :initarg
option in a slot specifier creates a keyword parameter that can be used to initialize the slot; multiple parameters don’t create a conflict, so the new slot specifier contains all the :initarg
s. Callers of **MAKE-INSTANCE**
can use any of the :initarg
s to initialize the slot. If a caller passes multiple keyword arguments that initialize the same slot, then the leftmost argument in the call to **MAKE-INSTANCE**
is used.
Inherited :reader
, :writer
, and :accessor
options aren’t included in the merged slot specifier since the methods created by the superclass’s **DEFCLASS**
will already apply to the new class. The new class can, however, create its own accessor functions by supplying its own :reader
, :writer
, or :accessor
options.
Finally, the :allocation
option is, like :initform
, determined by the most specific class that specifies the slot. Thus, it’s possible for all instances of one class to share a :class
slot while instances of a subclass may each have their own :instance
slot of the same name. And a sub-subclass may then redefine it back to :class
slot, so all instances of that class will again share a single slot. In the latter case, the slot shared by instances of the sub-subclass is different than the slot shared by the original superclass.
For instance, suppose you have these classes:
(defclass foo ()
((a :initarg :a :initform "A" :accessor a)
(b :initarg :b :initform "B" :accessor b)))
(defclass bar (foo)
((a :initform (error "Must supply a value for a"))
(b :initarg :the-b :accessor the-b :allocation :class)))
When instantiating the class bar
, you can use the inherited initarg, :a
, to specify a value for the slot a
and, in fact, must do so to avoid an error, since the :initform
supplied by bar
supersedes the one inherited from foo
. To initialize the b
slot, you can use either the inherited initarg :b
or the new initarg :the-b
. However, because of the :allocation
option on the b
slot in bar
, the value specified will be stored in the slot shared by all instances of bar
. That same slot can be accessed either with the method on the generic function b
that specializes on foo
or with the new method on the generic function the-b
that specializes directly on bar
. To access the a
slot on either a foo
or a bar
, you’ll continue to use the generic function a
.
Usually merging slot definitions works quite nicely. However, it’s important to be aware when using multiple inheritance that two unrelated slots that happen to have the same name can be merged into a single slot in the new class. Thus, methods specialized on different classes could end up manipulating the same slot when applied to a class that extends those classes. This isn’t much of a problem in practice since, as you’ll see in Chapter 21, you can use the package system to avoid collisions between names in independently developed pieces of code.