Previous Table of Contents Next


Polymorphism also yields a more precise definition of “instance.” A direct instance of a type A is an object created from the exact pattern defined by the declaration of A’s base class, with one field for each of the class attributes; you obtain it through a creation instruction of the form !! x..., for x of type A, or by cloning an existing direct instance. An instance of A is a direct instance of any type conforming to A: A itself, but also many types based on descendant classes. An instance of SAVINGS_ACCOUNT is also an instance, although not a direct instance, of ACCOUNT.

A consequence of polymorphism is the ability to define polymorphic data structures. With a declaration such as

   accounts: LIST [ACCOUNT]

the procedure call accounts.extend (acc), because it uses a procedure extend which in this case expects an argument of any type conforming to ACCOUNT, is valid not only if acc is of type ACCOUNT but also if it is of a descendant type such as SAVINGS_ACCOUNT. Successive calls of this kind make it possible to construct a data structure that, at runtime, might contain objects of several types, all conforming to ACCOUNT (see Figure 9.10).


FIGURE 9.10.  Polymorphic data structure.

Such polymorphic data structures combine the flexibility and safety of genericity and inheritance. They can be more or less general depending on the type, here ACCOUNT, chosen as actual generic parameter; static typing is again precious, prohibiting for example a mistaken insertion of the form accounts.extend (dep) where dep is of type DEPOSIT, which does not conform to ACCOUNT.

It remains possible to produce unrestrictedly polymorphic data structures, such as a general_list: LIST [ANY], which makes the call general_list.extend (x) valid for any x. The price to pay is that the operations applicable to an element retrieved from such a list are only the most general ones (assignment, clone, equality comparison, and the like)—although assignment attempt, studied later, makes it possible to apply more specific operations after checking that a retrieved object is the appropriate type.

9.9.4. Dynamic Binding

The complement of polymorphism and dynamic binding is the answer to the question “What version of a feature is applied in a call whose target is polymorphic?” For example, if acc is of type ACCOUNT, the attached objects may now, thanks to polymorphism, be direct instances not just of ACCOUNT but also SAVINGS_ACCOUNT or other descendants. Some of these descendants, indeed SAVINGS_ACCOUNT among them, redefine features such as deposit. What then is the effect of a call of the form acc.deposit (some_value)?

Dynamic binding is the clearly correct answer: The call executes the version of deposit from the generating class of the object attached to acc at runtime. If acc is attached to a direct instance of ACCOUNT, execution uses the original ACCOUNT version; if acc is attached to a direct instance of SAVINGS_ACCOUNT, the call executes the version redefined in that class.

This is a clear correctness requirement. A policy of static binding (as available, for example, by default in C++ or Borland’s Delphi) would take the declaration of acc as an ACCOUNT literally. But that declaration is only meant to ensure generality, to enable the use of a single name acc in many different cases: What counts at execution time is the object that acc represents. Applying the ACCOUNT version to a SAVINGS_ACCOUNT object would be wrong, possibly leading in particular to objects that violate the invariant of their own generating class (because there is no reason a routine of ACCOUNT will preserve the specific invariant of a proper descendant such as SAVINGS_ACCOUNT, which it does not even know about).

Note that in some cases the choice between static and dynamic binding does not matter: This is the case, for example, if a call’s target is not polymorphic or if the feature of the call is redefined nowhere in the system. In such cases, the use of static binding permits slightly faster calls (because the feature is known at compile time). This application of static binding should, however, be treated as a compiler optimization. Good Eiffel compilers detect such cases and process them accordingly—unlike approaches that make developers responsible for specifying what should be static and what dynamic (a tedious and error-prone task, especially delicate because a minute change in the software can make a static call, far away in another module of a large system, suddenly become dynamic). Eiffel developers are protected from such concerns; they can rely on the semantics of dynamic binding in all cases, with the knowledge that an optimizing compiler applies static binding when safe and desirable.

Even in cases that require dynamic binding, the design of Eiffel, in particular the typing rules, enables compilers to make the penalty over the static-binding calls of traditional approaches very small and, most importantly, constant bounded: It does not grow with the depth or complexity of the inheritance structure. The discovery in 1985 of a technique for constant-time dynamic binding calls, even in the presence of multiple and repeated inheritance, was the event that gave the green light to the development of Eiffel.


Previous Table of Contents Next