View
212
Download
0
Embed Size (px)
Citation preview
PolymorphismFrom now on we will use g++!
Example (revisited)
Goal:
• Graphics package
• Handle drawing of different shapes
• Maintain list of shapes
Solution #1
class Shape {public:
enum Type { e_square, e_circle, e_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. (switch command)
Pros:• Simple, directCons:• Adding new shapes requires changing all
procedures that deal with shape.• Require fat interface in order to support all kinds
of shapes.• No type checking. Unsafe!
Solution #2 – different classes
class 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 - Inheritanceclass 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 is using 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 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 is clearly not what we want to do in this example.
• A more desirable here solution,
Dynamic resolution (Polymorphism):
• Based on the type of the object.
• Determined at run time.
[Java Like!]
Dynamic Resolution Cont
• 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 not the intended 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 efficiency.
• Use of virtual destructor for base class!
Shapes & Sizes
Revisiting our example, we writeclass Shape {public:
…virtual ~Shape(); // virtual destructor
virtual void draw(); // virtual methodvirtual double area(); // also…
};
How do we implement Shape::draw() ?
Inheritance & Abstract Classes
• In this example, we never want to deal with objects of type Shape– Shape serves the role of an interface
• 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}
Abstract Classes
• We can specify that Shape::draw() does not exist
class Shape {public:…virtual ~Shape(); // virtual destructor
virtual void draw() = 0; // pure virtualvirtual double area() = 0; // Also…
};
Abstract Classes
• 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! (invalid object state)
• Use virtual methods Duplicate() and Create() for implementing “virtual constructors”.
• Use pure abstract classes to define interfaces.• Use inheritance with care: Be sure that this is
the appropriate solution.