Example (revisited)
We want to implement a graphics system We plan to have lists of shape. Each shape
should be able to draw it self, compute its size, etc.
Solution #1class Shape {
public:
enum Type { Square, Circle, Triangle };
Shape( Type t, Point Center,
double width, double height);
void Draw();
double Area();
…
private:
Type m_type;
Point m_center;
…
};
Solution #1
This solution gives a nice “wrapping” to the solution we consider in C
It does not solve the problems of that solution Lack of extendibility
Solution #2 – different classesclass Square {
public:
…
void Draw();
double Area();
…
};
class Circle {
public:
…
void Draw();
double Area();
…
};
Solution #2 – Discussion
Each shape has its own implementation We can easily add new shapes
However, Shapes are different types One cannot view them as part of the same
class
Solution #3 - hierarchyclass Shape {
public:
…
void Draw();
double Area();
…
};
class Square: public Shape {
public:
…
void Draw();
double Area();
…
};
class Circle: public Shape {
public:
…
void Draw();
double Area();
…
};
…
Solution #3
Now we can write code such as:
Shape* MyShapes[2];
MyShapes[0] = new Circle();
MyShapes[1] = new Square();
…
What will happen when we callMyShapes[0]->Draw();
Lets test … see Shapes.cpp.
What is going on?
When we write:Circle circle;
circle.Draw();
The compiler calls Circle::Draw()
When we write:Shape* p = new circle();
p->Draw();
The compiler calls Shape::Draw()Why? *p has type Shape
Class Hierarchyclass Base {
public:
…
void foo();
void bar();
…
};
class Derived: public Base {
public:
…
void bar();
…
};
Derived obj;
obj.foo();
Calls Base::foo() Derived does not
implement this method
obj.bar(); Calls Derived::bar() Two implementations,
the more specific is used
Inheritance & Overloading
Derived inherits the method foo() from Base
The inherited class to use methods of the base class
Derived overloads the implementation of bar()
This mechanism allows an inherited class to specialize the implementation
Static resolution
How does the compiler determine which method to call?
Static Resolution: Based on the type of the variable.
Not the type of the object! The compiler finds the most specific
implementation of the method, and calls it
Static resolution in our example
Circle* circle = new Circle(1,1,2);
Square* square = new Square(2,2,1);
Shape* MyShapes[2];
MyShapes[0] = circle;
MyShapes[1] = square;
circle->Draw(); // Calls Circle::Draw()
square->Draw(); // Calls Square::Draw()
MyShapes[0]->Draw(); // Calls Shape::Draw()
MyShapes[1]->Draw(); // Calls Shape::Draw()
Dynamic Resolution
This clearly not what we want to in this example
More desirable here is
Dynamic resolution: Based on the type of the object Determined at run time
[Java Like]
Dynamic Resolution in C++
The virtual keyword states that the method can be overloaded in a dynamic manner.
class Base {
public:
…
virtual void bar();
…
}
class Derived: public Base {
public:
…
virtual void bar();
…
}
Solution #4: dynamic resolution
Returning to the shapes example, using virtual methods gives the desired result
See Shapes-virtual.cpp
Virtual Methods
Class Base defines a virtual method foo() The resolution of foo() is dynamic in all
subclasses of Base If the subclass Derived overloads foo(),
then Derived::foo() is called If not, Base::foo() is called
[See nested.cpp]
Underneath the Hood: Inheritance
class Base {
…
private:
double m_x;
int m_a;
};
class Derived
: public Base {
…
private:
double m_z;
};
class Base a;
class Derived b;
m_x
m_a
m_z
m_x
m_a
a:
b:Base
Pointing to an Inherited Class
class Derived b;
class Base* p = &b;
When using *p, we treat b as though it was a Base object
The compiler cannot know if *p is from a derived class or not.
m_x
m_a
m_z
b:
p:
Implementation of Virtual Methods
Possible approach: If foo() is a virtual method, then each object
has a pointer to the implementation of foo() that it uses
Essentially the solution we used in our C implementation
Cost: Each virtual method requires a pointer
Large number of virtual methods
waste of memory
Implementation of Virtual Methods
Alternative solution: Each object has a pointer to an array of
function pointer This array points to the appropriate functions
Cost: For each class, we store one table Each object contains one field that points to
the right table
Exampleclass A { public: virtual void f1(); virtual void f2(); int m_a;};
class B: public A { public: virtual void f1(); virtual void f3(); void f4();
int m_b;};… A a1, a2; B b;
class A { public: virtual void f1(); virtual void f2(); int m_a;};
class B: public A { public: virtual void f1(); virtual void f3(); void f4();
int m_b;};… A a1, a2; B b;
void A::f1{ //...};
void A::f1{ //...};
void A::f2{ //...};
void A::f2{ //...};
void B::f1{ //...};
void B::f1{ //...};
VTBLs
A
B
<vtbl>
m_a
a1:
<vtbl>
m_a
m_b
b:
<vtbl>
m_a
a1:
void B::f3{ //...};
void B::f3{ //...};
f1
f2
f3
f1
f2
Virtual - Recap
Virtual controls whether to use static or dynamic resolution
Once a method is virtual, it must remain so throughout the hierarchy
Calling a virtual method is more expensive than standard calls Two pointers are “chased” to get to the
address of the function
Destructors & Inheritance
class Base {
public:
~Base();
};
class Derived : public Base {
public:
~Derived();
};
Base *p = new Derived;
…
delete p;
Which destructor is called? [see destruct.cpp]
Virtual Destructor
Destructor is like any other method The example uses static resolution, and
hence the wrong destructor is called To fix that, we need to declare virtual
destructor at the base class! See destruct-virt.cpp
Once you declare virtual destructor, derived class must declare a destructor
Polymorphism & Inheritance
C++ allows to use class hierarchy to implement polymorphic code
Points of care: Choice of virtual methods
Run time considerations Use of virtual destructor for base class
Shapes & Sizes
Revisiting our example, we writeclass Shape {
public:
…
virtual ~Shape(); // Virtual destructor
virtual void Draw(); // Virtual method
virtual double Area(); // Also
…
};
How do we implement Shape::Draw() ?
Inheritance & Interfaces In this example, we never want to deal with
objects of type Shape Shape serves the role of an interace
All shapes need to be specific shapes that are instances of derived classes of Shape
How do we enforce this?Simple mechanism:void Shape::Draw(){assert(false); // we should never call this method
}
Pure Virtual Classes
We can specify that Shape::Draw() does not exist
class Shape {
public:
…
virtual ~Shape(); // Virtual destructor
virtual void Draw() = 0; // pure virtual
virtual double Area() = 0; // Also
…
};
Pure Virtual
We cannot create objects of a Pure Virtual class
Shape* p; // legal
Shape s; // illegal
Circle c; // legal
p = &c; // legal
p = new Shape; // illegal
Virtual Methods - Tips
If you have virtual methods in a class, always declare its destructor virtual
Never call virtual methods during construction and destruction
Use virtual methods Duplicate() and Create() for implementing “virtual constructors”
Use pure virtual classes to define interfaces Use inheritance with care: Be sure that this
is the appropriate solution
Post Quiz issues….
Things to brush up on: Preprocessor vs. Compiler #define and how it works Compiler vs. Linkage
Example void main()
{
(1) int *y = (int*)malloc( 3*sizeof(int) );
(2) int x[] = {1,2,3};
(3) int ** pp = NULL;
(4) int *z = y;
}
A space on the memory heap was allocated in the following lines:
a. 1
b. 2
c. 3
d. 4
Another
int *ip = (int*)malloc(100*sizeof(int));
(*ip) += 30;
free(ip);
1 This code will generate a compilation error.
2 This code can generate unpredictable run-time behavior.
3 No error would occur if we only check if ip equals NULL before calling the "free" function
4 The code contains no error.
Something else….
char s[] = {'p','l','a','b'};
What will be returned by calling strcmp(s,"plab")?
1. a negative number
2. 0
3. a positive number
4. We cannot know for sure
Almost Last Examplestruct MyStruct {
int x[3];
};
void f(MyStruct s)
{
s.x[0] = 15; s.x[1] = 16; s.x[2] = 17;
}
int main()
{
MyStruct s;
f(s);
printf("%d %d %d\n", s.x[0], s.x[1], s.x[2]);
}
Similar but not quite…struct MyStruct {
int x[3];
};
void f(MyStruct* s)
{
s->x[0] = 15; s->x[1] = 16; s->x[2] = 17;
}
int main()
{
MyStruct s;
f(&s);
printf("%d %d %d\n", s.x[0], s.x[1], s.x[2]);
}