COMP2006 Lecture 9 Inheritance and Virtual Functions
COMP2006 Lecture 9 Inheritance and Virtual Functions
C++ Programming
Lecture 9
Dr Chao Chen
1
This lecture
• Inheritance
• Virtual functions
2
Inheritance
3
What is inheritance?
• Inheritance models the ‘is-a’ relationship
– i.e. the sub-class object is-a type of base class object
• Define a new class (sub-class/derived class) in terms of a
current class (superclass/base class)
• Why do it?
– Get some member functions and data of the base class, for free,
without having to (re-)write them
– Utilise sub-type polymorphism
• How can we extend it?
– Add new functionality?
– Change or refine existing functionality? (within reason)
– Remove functionality? (and still work as base class?)
4
Using inheritance
• Use the : notation (after the class name)
class MyClass : public MySuperClass
{
}; Maximum access level,
assume public for the moment
7
Base-class access rights
Think: public -> protected -> private
class MyClass : public MySuperClass
– “At most public access” (i.e. no change)
– public/protected members are inherited with the same
access as in the base class
– The most common form of inheritance
class MyClass : protected MySuperClass
– “At most protected access”
– public/protected members are inherited as protected
members of the sub-class
class MyClass : private MySuperClass
– “At most private access”
– public/protected members are inherited as private
members of the sub-class
8
Composition vs Inheritance
class Class1 class ContainerClass
{ {
public: public:
int iBase; Class1 c;
long lBase; int iSub;
}; };
Class1 c ContainerClass s
int iBase int c.iBase
long lBase long c.lBase
int iSub
void foo()
{
Class1 c;
Simple composition: the contained class
ContainerClass s;
part appears inside the containing class
9
}
Composition vs Inheritance
class BaseClass class SubClass
{ : public BaseClass
public: {
int iBase; public:
long lBase; int iSub;
}; };
BaseClass b SubClass s
int iBase int iBase
long lBase long lBase
int iSub
void foo()
{
BaseClass b;
Simple single-inheritance: the base class
SubClass s; part appears inside the sub-class 10
}
Sub-class objects ARE base class objects
class BaseClass class SubClass
{ : public BaseClass
public: {
int iBase; public:
long lBase; int iSub;
}; };
BaseClass b SubClass s
int iBase int iBase
long lBase long lBase
void foo() int iSub
{
SubClass* pSub = new SubClass();
BaseClass* pBase = pSub; // POINTERS!
// Same applies to references!
delete pSub; 11
}
Inheriting and overriding methods
class BaseClass class SubClass
{ : public BaseClass
{
public: public:
char* foo() char* foo()
{ {
return "BaseFoo"; return "SubFoo";
} }
char* bar() // No override for bar()
{ };
return "BaseBar";
} bar() from base class is available
}; unchanged in sub-class
sub-class “overrides” (replaces) the
int main() foo() function from the base class
{
SubClass* pSub = new SubClass;
printf("foo=%s bar=%s\n", pSub->foo(), pSub->bar() );
delete pSub;
} Using dynamically allocated memory 12
Virtual functions
13
Question: which function is called?
class BaseClass
{ Question:
public: When functions are
char* foo() { return "BaseFoo"; } called from base-class
}; pointers/references,
which functions are
class SubClass : public BaseClass called?
{
public:
char* foo() { return "SubFoo "; } i.e. what do these do?
};
pSub->foo()
int main()
{ pSubAsBase->foo()
SubClass* pSub = new SubClass;
BaseClass* pSubAsBase = pSub; // Pointers
15
Example: virtual functions
class BaseClass
{
public: char* foo() { return "BaseFoo"; }
virtual char* bar() { return "BaseBar"; }
};
int main()
{
SubClass* pSub = new SubClass;
BaseClass* pSubAsBase = pSub;
printf( "pSubAsBase->foo() %s\n", pSubAsBase->foo() );
printf( "pSubAsBase->bar() %s\n", pSubAsBase->bar() );
delete pSub; 16
}
Virtual functions – the cost of flexibility
• Adding a virtual function to a class may make
the objects of that class bigger
– This is very common when you add the first virtual
function
– We will see virtual function tables later
• This is one implementation of virtual functions
• It makes all objects bigger when the first virtual function is
added
– Some equivalent of this system has to exist for
implementing virtual functions
• Looking up which function to call at runtime
is slower when functions are virtual
– This is why the default is to not have virtual
functions
17
Calling base-class functions
• Even if a function is virtual, you can still call the
base class version from the sub-class version
– Useful so that you don’t need to repeat code
• From Java you can call the (immediate) super-
class version of a method from within a method
– Uses the super.foo() notation
• The C++ version is more flexible…
– You can call any base-class version, not just the
immediate base-class
• C++ uses the scoping operator ::
– Example…
18
Example of scoping operator
class Base
{
public:
virtual void DoSomething() Base class version of
{ x = x + 5; } DoSomething() adds 5 to x
private:
int x;
};
class Derived : public Base
{
public:
virtual void DoSomething() Derived class version of
{ DoSomething() adds 5 to y
y = y + 5; THEN calls the base class
version, which will add 5 to x
Base::DoSomething();
}
private:
int y; This EXPLICITLY calls the base-class version 19
};
Scoping
#include <cstdio> void modify()
int i = 1; // Global {
int i = 7; // Local
struct Base ::i = 4; // Global
{ Sub::i = 5; // Sub's i
int i; Base::i = 6; // Base's i
Base }
Base() int i };
: i(3)
{} int main()
}; {
Sub s;
struct Sub : public Base printf( "%d %d %d\n",
{ i, s.i, s.Base::i );
int i; Sub s.modify();
printf( "%d %d %d\n",
Sub() (Base) int i i, s.i, s.Base::i );
: i(2) (Sub) int i return 0; 20
{} }
Reminder: The scoping operator
• You can use the scoping operator to call
global functions or access global variables
– use :: with nothing before it
• Also used to denote that a function is a
class member in a definition, e.g.
void Sub::modify() { … }
• Left of scoping operator is
– blank (to access a global variable/function)
– class name (to access member of that class)
– namespace name (to use that namespace) 21
Inheritance and constructors
22
Construction and destruction (1)
struct Base
{
Base()
{ printf("Base constructed\n"); }
int main()
{
Created on the heap
Derived* pD = new Derived;
delete pD; instead of the stack
}
25
Construction and destruction (2)
Source Code:
struct Base
{ Derived* pD =
Base() new Derived;
{ printf( "Base constructed\n" ); } delete pD;
~Base()
{ printf( "Base destroyed\n" ); } Purpose:
};
Create object d, then
destroy it
struct Derived : public Base
{ Output:
Derived()
{ printf("Derived constructed\n"); } Base constructed
Derived constructed
~Derived()
{ printf("Derived destroyed\n"); }
Derived destroyed
};
Base destroyed 26
Constructors and destructors
• Construction occurs in the order:
– Base class first, then derived class
• Destruction occurs in the order:
– Derived class first, then base class
• Effects:
– Derived class part of the object can always assume that base
class part exists
• Derived class can assume that the base class has been constructed
when the derived class is constructed
• Derived class can assume that the base class has not yet been
destroyed at the point the derived destructor is used
– Derived class will NOT exist/be initialised when the base class
constructor/destructor is called, so:
– Do not call virtual functions from the
constructor or destructor 27
Construction and destruction (3)
Source Code:
struct Base
{ Base* pD =
Base() new Derived;
{ printf( "Base constructed\n" ); }
delete pD;
~Base()
{ printf( "Base destroyed\n" ); } Purpose:
};
Create object d, then
destroy it through a
struct Derived : public Base base class pointer
{
Derived()
Output:
{ printf("Derived constructed\n"); }
};
~Derived()
{ printf("Derived destroyed\n"); } ?
28
Construction and destruction (3)
Source Code:
struct Base
{ Base* pD =
Base() new Derived;
{ printf( "Base constructed\n" ); }
delete pD;
~Base()
{ printf( "Base destroyed\n" ); }
};
Output:
30
Construction and destruction (4)
struct VirtualBase
{
VirtualBase()
{
printf("Base constructed\n");
} Virtual Destructor
virtual ~VirtualBase()
{
printf("Base destroyed\n");
}
};
~VirtualDerived()
{ printf("Derived destroyed\n");}
}; 32
Construction and destruction (4)
struct VirtualBase Source Code:
{
VirtualBase() VirtualBase* pD = new
{ printf("Base constructed\n"); } VirtualDerived;
delete pD;
virtual ~VirtualBase()
Purpose:
{ printf("Base destroyed\n"); }
};
Create object d, then destroy it
struct VirtualDerived through base class pointer.
: public VirtualBase
{ Output:
VirtualDerived()
Base constructed
{printf("Derived constructed\n");}
Derived constructed
Derived destroyed
~VirtualDerived()
Base destroyed
{ printf("Derived destroyed\n");}
}; 33
Virtual destructors
34
Virtual destructors: Question
• Should we make the destructor virtual or not?
• My advice: (only advice!!!)
– Make it virtual if and only if there are ANY other virtual
functions
• No loss since vtable pointer already exists anyway
• Probably using object through a base class
pointer/reference, so object potentially COULD be
destroyed that way too
– If there are no other virtual functions
AND you do not expect the object to be deleted
through a pointer or reference to the base class
THEN do not make your destructor virtual
• Otherwise you add an unnecessary vtable pointer (or
equivalent) to objects
35
Example when virtual is useful
36
Next lecture
• Function pointers
37