Title: Classes and Inheritance
1Classes and Inheritance
2Recall Structs
- Many programming languages support user-defined
types that are aggregations of other objects. - supported in C with the struct construct
- supported in C with the class construct
- The aggregation allows several variables to be
treated as a unit - allocated together
- passed as parameters/return values together.
3Implementation of Structs
- Compiler determines a size of the aggregate
object. - size of the field
- some adjustments for address alignment
- Compiler determines an offset for each field.
- The . operator is implemented simply by adding
the offset of the named field to the address of
the aggregate.
4Type Rules in C
- Structs in C are legitimate types and can be
use exactly like any base types. - can be parameters, global/local variables or
return types - can be fields in other structs
- derived types (pointer reference) can be based on
structs. - Struct assignment is permitted in C. The
default behavior is to use the default
assignment/copy rules for each individual field. - Custom behavior can be defined using operator()
- Strong/static type checking is used.
5Member Functions
- The next step up on the evolutionary ladder above
structs is ADTs. - An Abstract Data Type is a software module
consisting of a struct (data layout) plus a set
of functions for operating on that struct. - These functions (obviously) require that one of
their parameters be a pointer or reference to the
struct that they are to manipulate. - Member functions support ADTs by
- merging the source code for the data layout and
function declarations. - passing the struct pointer implicitly to member
functions
6C and this
- The this pointer is a parameter. For a member
function in class T, the - declared T const this
- The this pointer is passed implicitly (does not
appear in the parameter list). - The implicit this pointer is the only
implementation difference between a member
function and a regular function! - The only other differences relate to
type-checking rules (scoping and access control).
7Pop Quiz
- int x 0
- class A
- void doit(void) x 1 doit()
- // no data members
- class B
- double a,b,c // 24 bytes of data members
- void doit(void) x 1 doit()
-
- Does either function run indefinitely? Which
program runs longer (increments x more times)
before crashing? Adoit() or Bdoit()?
8Inheritance
- The terminology of object-oriented programming is
somewhat ambiguous (at least in conventional
usage). - The C inheritance mechanism does at least two
very distinct things. - Allows a new type to be defined as an extension
of an existing type. - Creates a subtype/supertype relationship.
- Ill refer to 1 as inheritance and as 2 as
subtyping.
9Inheritance for Reuse
- Remember that the goal for object-oriented
programming was to achieve greater/more flexible
code reuse. - Imagine we have a class that provides some useful
function, but that we wish to improve, e.g.,
adding range checking to the standard vector
container.
10Defining an Inheritance Relationship
- class Derived public Base
- // changes from Base class
-
- Public inheritance makes all of Bases public
members available to users of Derived (public in
base becomes public in Derived). - The body of Derived describes changes from Base
- Except as explicitly changed, all of Base is
inherited into Derived.
11Restrictions and caveats on Inheritance
- Constructors cannot be inherited. Must use ugly
syntax for constructors. - DerivedDerived(derived_args)
- Base(base_args)
- Assignment operator (and other operators) are
inherited. Can lead to some confusion with
return types. More on this in later examples. - Destructor is not inherited.
- Base constructor is invoked FIRST, base
destructor is invoked LAST.
12Checked Vector Example
- template lttypename Tgt
- class CheckedVector public vectorltTgt
- public
- explicit CheckedVector(int sz 0)
- vectorltTgt(sz)
- CheckedVector(const CheckedVectorltTgt other)
- vectorltTgt(other)
- CheckedVectorltTgt operator(const
CheckedVectorltTgt other) - (void) vectorltTgtoperator(other)
- return this
-
-
- Note the syntax to call the operator function
from the base class.
13Specifying Changes
- For our checked vector we want to inherit almost
all of the functionality from vector. operator
is about the only thing that should change. - T operator(unsigned k)
- if (k gt size()) abort()
- return vectorltTgtoperator(k)
-
- We need to watch out! Changes are based on the
NAME of the method, not the signature.
14Implementing Inheritance
- The compiler has two responsibilities with
inheritance. - Extend the struct by adding any new fields (note,
it is not possible to remove or eliminate old
fields) - Support inheritance by allowing the old methods
to be invoked on the new object WITHOUT
RECOMPILATION.
15Extending the Struct
- With single inheritance, this support is fairly
simple. All of the new fields are appended to
the end of the old object. - We will talk about this in terms of A Derived
object has a Base inside it. - To invoke a Base function, nothing special is
required, just pass this as we normally would
(with single inheritance). - All the original fields are accessed using their
original offsets!
16The Implied Subtype Relationship
- This simple implementation trick means that a
Derived object can be used any place that a Base
object was expected (their this pointers are the
same). - C provides a subtype (the isa) relationship
a Derived is a Base - Note the reverse relationship would not hold (all
base objects may not be derived). - During type checking, a subtype (derived) can be
used whenever a supertype is expected (even on
the right hand side of an equals!)
17Truncating objects
- The last bullet on the previous slide had a very
important point. A base object can be assigned
(copied) from a derived object. - However, the newly created object will only have
the base part of the original object. - Any additional fields will NOT be copied.
- Pass by value parameters are a common place to
run into this problem (well see this again
later).
18Multiple Inheritance (yuck)
- There are two distinct problems with multiple
inheritance. - A potential ambiguity arises as a natural result
of inheriting members from two (or more) base
classes. - The C compilation technique does not elegantly
support multiple inheritance (like it does for
single inheritance).
19Ambiguities with Multiple Inheritance
- class Base1
- void doit(void) cout ltlt Im number 1\n
-
- class Base2
- void doit(void) cout ltlt nope, number 2\n
-
- class Derived public Base1, Base2
-
- Derived d
- d.doit() // what happens?
- Programmer must explicitly state which doit is to
be used. - d.Base1doit() d.Base2doit()
20More Ambiguities and Virtual Inheritance
- class Counter
- public
- static int x
- Counter(void) x 1
-
- int Counterx 0
- class B1 public Counter
- class B2 public Counter
- class D public B1, B2
- D d // how many times is x incremented?
- Every D object has two Counters! (x 2).
- Instead, use virtual inheritance
- class B1 public virtual Counter
- class B2 public virtual Counter
21Implementation Nightmares,Fudging this
- Besides the potential for confusion, multiple
inheritance greatly complicates the C
implementation. - Lets assume we have two base classes B1 and B2
in derived class D. - B1s inherited fields will come first in object D
- B2s inherited fields will come second
- Ds new fields will be third.
- If we invoke an inherited B1 method on D, the
this pointers are the same. BUT if we invoke
an inherited B2 method, we need to change this!
22Implementation Pop Quiz
- Explain what it would take to implement a pointer
to a member function? - First, we must accept that pointers to member
functions must be kept distinct from pointers to
functions. - The implicit parameter must be passed to member
functions. - Next we need to consider the possibility that the
pointer to the function could point to an
inherited function. - in the case of multiple inheritance, we might
have to fudge this to call that function!
23Other Inheritance Stuff
- nested types are inherited
- CheckedVector inherits vectorltTgtiterator
- can be overridden.
- A base class can be an instantiation of a
template (duh, weve done this already).
24Putting Inheritance to good use
- The iterator base classes
- it is tedious to have to define all the umpteen
different nested types (value_type,
iterator_category, pointer, reference, etc.) for
your user-defined iterators. - Instead, inherit them!
- class iterator
- public iteratorltforward_iterator_tag, Tgt
-
25Protected Members
- To a first approximation, a protected member
(function or data) is accessible by the class and
any derived classes. - class B
- protected
- int x
-
- class D public B
- int value(void) return x
-
- Fields are often made protected (rather than
private).
26The technical definition
- class B
- protected int x
- void bFun(D d)
-
- class D public B
- void dFun(B b)
-
- The variable used to access x must be a subtype
of the method where the function is called. - BbFun(D d)
- this-gtx // OK
- d.x // OK
-
- DdFun(B b)
- this-gtx // OK
- b.x // ERROR
-
27Dynamic Binding
- Recall that the C inheritance mechanism creates
a subtype relationship - Any object of the derived type can be passed to a
function that expects a parameter of the base
type. - Note that type checking is still static, and the
function-call mechanism is still 100 compile
time. - In C we use virtual functions to get run-time,
type-specific behavior.
28The virtual function
- non-static member functions can be declared
virtual. - any function with the same signature in derived
classes must have the same return type. - when a virtual function is invoked through a
pointer or reference to a base type object, the
correct (inherited or overridden) function is
executed on the derived object. - the determination of which function to invoke is
made at run time.
29virtual example
- class B
- public
- //virtual
- void doit(void) cout ltlt "base\n"
-
- class D public B
- public
- void doit(void) cout ltlt "derived\n"
-
- void foo(B b)
- b.doit() // if doit is virtual, prints derived
-
- int main(void)
- D d
- foo(d)
-
30Style Suggestions with Virtual
- the keyword virtual is only required in the base
class. - use virtual in class definition for every derived
class. - virtual-ness applies to the signature, not the
name. - avoid confusion by not overloading virtual
functions
31Implementation of Virtual
- The compiler produces machine code that calls the
appropriate function using a pointer to the
function. - The pointer is taken from a table.
- each derived class has a distinct table.
- Every object has a pointer to the correct table.
32Implications of Implementation
- The size of the vf table is linear in the number
of virtual functions. (e.g. 4X bytes or 8X bytes
where there are X virtual functions). - There is only one table per class. There is one
pointer (to a table) inside each object. Thus
the objects are made 4 bytes larger when there is
a virtual function. - The virtual function table provides us the
ability to determine the type of an object at
runtime.
33Replacing Switch Statements with Virtual Functions
- A switch statement is inconsistent with the
principles of object-oriented programming. - Instead of
- switch(x-gttype)
- case A
- a_operation() break
- case B
- b_operation() break
-
- Create derived types A and B, and virtual
function operation in base class (x is ptr to
base class). - x-gtoperation()
34Functions invoked using this get dynamic binding
- Functions inherited from the base class can call
virtual functions from the derived class. - class B
- public
- virtual const char name(void)return "base"
- void display_info()
- cout ltlt "this is the " ltlt name() ltlt "
class\n" -
-
- class D public B
- public
- virtual const char name(void) return
"derived" -
35Virtual Destructors
- The rule of thumb for C is to make your
destructor virtual if you have at least one
virtual function. - Virtual destructors are important only if you use
new/delete. - When you call delete the destructor is invoked.
- If you delete a Base pointer that points to a
Derived object, we still want the derived
destructor to be called.
36A C Gotcha with Destructors
- Once upon a time I wrote a class that did not
need a destructor. I created a derived class
that also did not need a destructor. - I created instances of the derived class using
new, and used pointers of the base class to
reference these objects. - I deleted the objects using the pointers I had
but was surprised to discover a memory leak. - I fixed the memory leak by writing a virtual
destructor in the parent class with an empty body
. The derived class still did not need a
destructor.
37Abstract Functions
- In object oriented design it is common to have a
base type which is so general it is abstract.
i.e., youd never really have an object that was
just that type. For example, Shape (you could
have a Circle or a Triangle which might be
subtypes of Shape). - It may not make sense or be possible to implement
all of the functions in this abstract class. - A function is pure virtual if it has no
implementation.
38Pure Virtual Syntax and Semantics
- Syntax
- class Shape
- public
- virtual void draw(void) 0
-
- If any virtual function is pure virtual, then the
class is abstract. You cannot have a variable,
or return value declared an abstract type. - Any derived type must override the pure virtual
function or else it too will be abstract.
39When Should a Function be Abstract
- Keep in mind the consequences if any one
function is abstract, then the class is abstract - If there is no reasonable default implementation
for the function, or - if every subclass should be required to implement
this function.