Chapter 12 - Implications of Inheritance
While the principle of substitutibility makes sense, implementing it
sometimes is difficult
- Memory Layout
- How much storage should be allocated for a variable of some class?
- Stack allocated variables (static) are more efficient (both in terms
of the actual allocation process and in accessing during run.)
Therefore, most language designers want to do static allocation.
- To do stack allocation, the amount of storage needed must be
determined at compile time. For int, float, etc. this is known but
for a class (especially if subclassed) ??? What about if it is
polymorphic??
- Three possible answers to how much memory is enough
- Allocate enough for the base class. Ignore any needs of the
subclasses. (Minimum Static Space Allocation)
- Allocate the maximum amount needed for any subclass used.
(Maximum Static Space Allocation)
- Allocate only enough space for a pointer. Allocate space for
the object the pointer points to during run-time (dynamically).
(Dynamic Memory Allocation)
- Minimum Static Space Allocation - C++
- C++ focuses on efficiency so only allocates enough memory
for the actual declared class type for instance variables.
These variables have no potential for polymorphism
- To get polymorphism, the programmer must declare a pointer.
- If you assign a pointer to a parent to an instance of the
child, the default behavior is to "truncate" the child to fit.
This truncation is called "slicing".
Otherwise, you must overload assignment to get the desired
behavior.
- Information is lost. Could be a problem.
- Only the methods defined in the base class of the pointer
(ie. the pointers static type) can be invoked using the
pointer. You can't get at the "additions" to the child using
a pointer.
- Overriden methods in the child are available using a pointer
IFF the methods are marked as virtual in the parent.
- Maximum Static Space Allocation - not implemented
- Idea has merit in that no information is lost since the
variable is big enough to hold the largest subclass.
- Problem is determining the largest class. Must be able to
"see" the entire program not just a single compiled module.
- Dynamic Memory Allocation - Object Pascal, Java, Smalltalk,
Objective-C
- No values for objects are stored on the stack. All values are
stored on the heap (dynamic allocation). Only pointers are
on the stack.
- Every time you "change" what a pointer is pointing to, the
old memory is deallocated and new memory large enough for the
new value is allocated.
- The problem of pointer semantics arises in the method (as well
as in the use of pointers in the C++ method).
- Assignment
- 2 Semantic models for assignment
- Copy Semantics - Assignment creates a copy of the object being
assigned. There are 2 distinct objects as a result.
- Pointer Semantics - Assignment simply sets the left hand side to
the value of the right hand side. You create an alias. Both
pointers are set to point to the same storage location. Any
change to one are reflected by the other.
- Assignment in C++
- Default assignment of instances of classes copies the
corresponding fields - overloading can create any desired
operation - you should provide your own and not rely on the
default assignment
- Overloading the assignment operator has no affect on the
initialization operator (which is =)
- Parameter passing is a form a assignment (as is return by value)
- reference declaration/initialization uses pointer semantics
- Pointers use pointer semantics
- Slicing problem in C++, determining which method to execute in
a function depends on whether you send a value or a reference
as the formal parameter.
- The run-time system provides a default copy constructor but you
can (and should) define your own
- Equality
- Pointer equivalence - 2 pointers are equivalent if they point to the
same object - morning star and evening star both point to Venus and
are equal
- Bit-wise equality - for numbers and character strings, if the
evaluation of the bits are equal then the things are equal
- Structural equivalence (member equality) - if all the members of
one object have bit-wise equality with the corresponding members of
another object, then they are equal
- In oop world, are 2 objects equal if they have different dynamic
types and should equality operator take this into account?
- Equality often has no intrinsic meaning - overloading the == operator
is virtually mandatory
- Covariance and contravariance - messing with overriding
- Covariance - if the argument to the "overriden" method in the
child class is more general than that of the parent
- Contravariant - if the argument to the "overriden" method in
the child class is less general than that of the parent (this
is the example for shape/triangle/square)
- None of the languages discussed in Budd allow this situation so
skip except for general information.
- C++ allows the return type of overriden methods to differ from
that of the parent which is a sort of contravariance
- Equality in C++
- There is no default meaning for equality
- You must overload the == operator
- == is never polymorphic - it is always bound to the static type
- Type Conversion
- It is illegal to assign a parent to a child in statically typed
oop languages like C++
- How to break the "rule" - use a cast in C++ which is really just
invoking the "correct" constructor
- This discussion uses the C++ dynamic_cast that is part of the RTTI
system. Don't know much about this. Can achieve this by creating
a constructor in the child class that accepts the parent as a
parameter and using the pertinent information.