Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
8 views

COMP2006 Lecture 9 Inheritance and Virtual Functions

This lecture covers inheritance and virtual functions in C++. Inheritance allows a subclass to inherit properties and behaviors from a base class through the "is-a" relationship. Virtual functions allow polymorphic behavior where calling code interacts with an object through a base class interface and the actual function called is determined by the object's type. The lecture discusses inheritance syntax, access specifiers, overriding vs calling base class functions, and how virtual functions are implemented using virtual function tables.

Uploaded by

zombiten3
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views

COMP2006 Lecture 9 Inheritance and Virtual Functions

This lecture covers inheritance and virtual functions in C++. Inheritance allows a subclass to inherit properties and behaviors from a base class through the "is-a" relationship. Virtual functions allow polymorphic behavior where calling code interacts with an object through a base class interface and the actual function called is determined by the object's type. The lecture discusses inheritance syntax, access specifiers, overriding vs calling base class functions, and how virtual functions are implemented using virtual function tables.

Uploaded by

zombiten3
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 37

COMP2006

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

• Equivalent of Java’s ‘extends’, i.e.:


class MyClass extends MySuperClass
• A class can have multiple base classes
– See later lecture – some complexities
5
Inheritance
• Define a new class (sub-class or derived class)
in terms of a current class (super/base class)
• Inheritance models the ‘is-a’ relationship
– If we have a class which models a bird,
– And we want a class for a specific species of bird
– Then we can take the general class and extend it
class FlyingBird
class Bird
: public Bird
{
{
public:
public:
void eat();
void fly();
void sleep();
};
};
6
Note: Function implementation is probably in associated .cpp files
A new access type: protected
• Reminder: public access
– Anything can access the member
• Reminder: private access
– Only class members can access the members
– NOT even sub-class members
– The main reason for being a class member
• New: protected access
– Like private but also allows sub-class members
to access the members

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

printf( "foo S=%s SaB=%s\n",


pSub->foo(), pSubAsBase->foo() );
delete pSub;
14
}
How to determine which?
You can choose which you want to apply
(by making the function virtual or not)

a) The functions in the sub-class are used


(determined by the object type)
If the functions are virtual

b) The functions in the base-class are used


(determined by the pointer type)
If the functions are NOT specified as virtual

15
Example: virtual functions
class BaseClass
{
public: char* foo() { return "BaseFoo"; }
virtual char* bar() { return "BaseBar"; }
};

class SubClass : public BaseClass


{
public: char* foo() { return "SubFoo"; }
virtual char* bar() { return "SubBar "; }
};

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"); }

~Base() Base class with


{ printf("Base destroyed\n"); } constructor and destructor
};
struct Derived : public Base
{
Derived()
{ printf("Derived constructed\n"); }
Sub-class of Base
~Derived()
{ printf("Derived destroyed\n"); }
};
int main()
{ Create object of Derived/Sub-class
Derived d;
} 23
Construction and destruction (1)
Source Code:
struct Base
{
Base() { Derived d; }
{ printf( "Base constructed\n" ); }
Purpose:
~Base()
{ printf( "Base destroyed\n" ); } Create object d, allow it to
}; be destroyed as stack frame
exits.
struct Derived : public Base
{ Output:
Derived()
{ printf("Derived constructed\n"); } Base constructed
Derived constructed
~Derived()
{ printf("Derived destroyed\n"); }
}; Derived destroyed
Base destroyed
24
Construction and destruction (2)
struct Base
{
Base() { printf( "Base constructed\n" ); }

~Base() { printf( "Base destroyed\n" ); }


};

struct Derived : public Base


{
Derived() { printf("Derived constructed\n"); }

~Derived() { printf("Derived destroyed\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:

struct Derived : public Base Base constructed


{ Derived constructed
Derived() Base destroyed
{ printf("Derived constructed\n"); }
Derived NOT destroyed
~Derived()
{ printf("Derived destroyed\n"); }
};
29
Question
• How do you think we can fix this issue?

– The derived class destructor is not called

30
Construction and destruction (4)
struct VirtualBase
{
VirtualBase()
{
printf("Base constructed\n");
} Virtual Destructor

virtual ~VirtualBase()
{
printf("Base destroyed\n");
}
};

struct VirtualDerived : public VirtualBase


{

} VirtualBase* pD = new VirtualDerived;
delete pD;
31
Construction and destruction (4)
struct VirtualBase Source Code:
{
VirtualBase() VirtualBase* pD =
{ printf("Base constructed\n"); } new 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()
?
{printf("Derived constructed\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

• If destructor is NOT virtual then it will NOT be


called if the object is destroyed through a base
class pointer, reference or function
– Since type of pointer/reference/function will
determine the destructor to call

• But, if you make destructor virtual then the


objects of that class will have a (hidden) vtable
pointer (or equivalent)
– i.e. they grow

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

• Virtual function tables

37

You might also like