You are on page 1of 21

Mihai TOGAN

Cap. 7 – Metode virtuale. Polimorfism. Clase abstracte

Obiective:
 Suprascrierea metodelor ȋn clasele derivate
 Apelul metodelor moştenite/suprascrise
 Metode virtuale. Polimorfism
 Conceptul de dinamic binding (late/runtime binding)
 Apelul metodelor virtuale. Mecanismul v-table
 Destructori virtuali
 Metode virtuale pure
 Clase abstracte
 Clase de tip interfaţă

1
Mihai TOGAN
Suprascrierea metodelor ȋn clasele derivate.
Apelul metodelor moştenite/suprascrise (calling overrided methods)

Reluăm în continuare un exemplu mai vechi, folosit anterior:

#include <iostream>
using namespace std;

string stringify (int value)


{
char pvalue[100];
sprintf_s (pvalue, sizeof (pvalue), "%d", value);
return string (pvalue);
}

/*****************************************************************************/
class Vehicle
{
protected:
int m_Year;
string m_Color;

public :
Vehicle( const string &color, const int year)
: m_Year(year), m_Color(color) {}

const string getDesc() const {


string str = m_Color;
str += " from "; str += stringify(m_Year);

return str;
}

const string &getColor() const {return m_Color;}


const int getYear() const {return m_Year;}
//...
};

/*****************************************************************************/
class Car : public Vehicle
{
string m_Model;
int m_Power;
public :
Car(const string &color, const int year,
const string &model, const int power)
: Vehicle(color, year), m_Model(model), m_Power (power) {}

const string &getModel() {return m_Model;}


const int getPower() {return m_Power;}

const string getDesc () const {


string str = m_Model;
str += " from "; str += stringify(m_Year);
str += " having "; str += m_Color; str += " color (power: ";
str += stringify(m_Power);
2
Mihai TOGAN
str += " kW)";

return str;
}
//...
};

/*****************************************************************************/
void main ()
{
Car C ("Black", 2006, "Toyota Avensis", 100);
cout << C.getDesc().c_str() << endl;

cout << endl;


Vehicle *p = &C;
cout << p->getDesc().c_str() << endl;

/*****************************************************************************/

Rezultatul obţinut este:

Observaţie: metoda Vehicle::getDesc( ) este suprascrisă la nivelul clasei derivate Car::getDesc( ).

Explicaţie:
 În cazul 1, obiectul C este de tip Car. Compilatorul generează cod de apel pentru metoda
din Car.
 În cazul 2, pointerul p este de tip Vehicle. Compilatorul generează cod de apel pentru
metoda din Vehicle.

Dacă modificăm declaraţia metodei la nivelul clasei de bază:


class Vehicle
{
//...
virtual const string getDesc() const {
//...
}
};

Rezultatul este cel corect:

3
Mihai TOGAN
Observaţie: indiferent prin ce tip de date, Car sau Vehicle, accesăm obiectul, se apelează
metoda corectă specifică exact pentru obiectul instanţiat (!!)
Modificăm din nou programul: mai adaugam o clasa nouă Truck, derivată din clasa Vehicle
class Car : public Vehicle
{
//...
}

class Vehicle
{
//...
}

class Truck : public Vehicle


{
int m_Tonnage;
public:
Truck(const string &color, const int year, const int tonnage)
: Vehicle(color, year), m_Tonnage (tonnage) {}

const string getDesc () const {


string str = "Truck";
str += " from "; str += stringify(m_Year);
str += " having "; str += " tonnage ";
str += stringify(m_Tonnage);
return str;
}
//...
};

/*****************************************************************************/
void main ()
{
Vehicle *pVehicle;
bool flag;

//...
cin >> flag;
if (flag == true)
pVehicle = new Car ("Black", 2006, "Toyota Avensis", 100);
else
pVehicle = new Truck ("Green", 2009, 30);

cout << pVehicle->getDesc().c_str() << endl;

//...
cin >> flag;
if (flag == true)
pVehicle = new Car ("Black", 2006, "Toyota Avensis", 100);
else
pVehicle = new Truck ("Green", 2009, 30);

cout << pVehicle->getDesc().c_str() << endl;


}
/*****************************************************************************/
4
Mihai TOGAN

Acum, rezultatul obţinut este cel din figura de mai jos:

Observaţii:
 În acest caz, comportamentul codului este polimorfic: aceeaşi secvenţă de cod se execută
diferit.
 Explicaţie: cele două secvenţe de cod executate sunt identice însă în fiecare secvenţă de
cod pointerul pVehicle conţine adrese la obiecte diferite.

5
Mihai TOGAN
Metode virtuale
/**************************************************************************/
class Baza {
//...
public:
void F1() { cout << "\n F1 din clasa baza";}
virtual void F2() { cout << "\n F2 din clasa baza";}
//...
};

class Derivat : public Baza {


//...
public:
void F1() { cout << "\n F1 din clasa derivata";}
void F2() { cout << "\n F2 din clasa derivata";}
//...
};
/*************************************************************************/
void main ()
{
Baza B, *pBaza;
Derivat D;

pBaza = &B;
pBaza->F1(); // executa Baza::F1
pBaza->F2(); // executa Baza::F2
cout << endl;

pBaza = &D;
pBaza->F1(); // executa Baza::F1
pBaza->F2(); // executa Derivat::F2
cout << endl;

Baza &rBaza = D;
rBaza.F1(); // executa Baza::F1
rBaza.F2(); // executa Derivat::F2
cout << endl;
}
/***************************************************************************/

Diferenţa o face tipul metodei F2 (metodă virtuală la nivelul clasei de bază).


Polimorfism = „many shapes”. Ȋn cazul de mai sus: capabilitatea unui obiect de a avea mai
multe tipuri.
Dacă există o funcție care asteaptă un obiect Vehicle, atunci se poate folosi fară nici-o
problemă un obiect Car ȋn locul acestuia (motivul este că întotdeauna un Car este în primul
rând un Vehicle).

Observaţie: oriunde se poate folosi un pointer Vehicle * , se poate folosi și un pointer Car * .
6
Mihai TOGAN
Mecanismul v-table (tabela de funcții virtuale)

Fiecare clasă care are declarată o metodă virtuală (care nu era deja virtuală de la o clasă de
bază de mai jos) primeşte automat de la compilator o tabelă de pointeri la funcții: v-table
(__vfptr).

Pentru a inspecta uşor această tabelă am folosit modul de lucru debug din Visual Studio (vezi
figurile următoare).

Secvenţele de cod de mai jos sunt preluate folosind execuţia codului din exemplul anterior.

Secvenţa 1 : pBaza = &B

7
Mihai TOGAN
Secvenţa 2 : pBaza = &D

8
Mihai TOGAN
Secvenţa 3: Baza &rBaza = D;

9
Mihai TOGAN
Destructructori virtuali

Ştim deja că:


 Metodele de tip constructor și destructor nu se mostenesc.
 La instanţierea unei clase derivate, se apelează ȋn mod automat constructorul și
destructorul clasei de bază:
class Baza {
//...
Baza();
~Baza();
};

class Derivat: public Baza {


//...
Derivat();
~Derivat();
};

void main ()
{
Derivat D;
}

În acest caz, apelurile constructorilor şi destructorilor sunt realizate în modul următor:


Baza(), Derivat().... ~Derivat(), ~Baza()

Dacă schimbăm puţin codul funcției main:


void main ()
{
Baza *pB = new Derivat(); // instantierea unui obiect Derivat
//...
delete pB; // distrugerea obiectului Derivat
}

Stiva de apeluri devine atunci:


Baza(), Derivat()....~Baza()

Explicaţia: pointerul pB este de tip Baza, şi din această cauză destructorul clasei derivate nu
mai este apelat de compilator.
Dezavantajul care se crează: nu se realizează distrugerea corectă/completă a obiectului (nu se
face cleanup la nivelul componentei provenite din clasa Derivat).

Soluţia: este necesar ca și destructorul clasei Baza sa fie virtual (implică includerea acestuia
ȋn tabela de funcţii virtuale v-table).
class Baza {
//...
Baza();
virtual ~Baza();
};
10
Mihai TOGAN
class Derivat: public Baza {
//...
Derivat();
~Derivat();
};

void main ()
{
Baza *pB = new Derivat(); // instantierea unui obiect Derivat
//...
delete pB; // distrugerea obiectului Derivat
}

În acest caz, stiva de apeluri pentru constructori/destructori este:


Baza(), Derivat()....~Derivat(), ~Baza()

Ȋn plus, dacă se derivează și mai departe, trebuie ca și destructorul lui Derivat sa fie
virtual:

class Derivat: public Baza {


//...
virtual ~Derivat();
}

class Subderivat: public Derivat {


//...
}

Recomandare: este bine ca orice clasă sa aibe destructorul virtual.

11
Mihai TOGAN
Metode virtuale pure și clase abstracte

Reluăm din nou un exemplu anterior:

class Vehicle
{
protected:
int m_Year;
string m_Color;

public :
Vehicle( const string &color, const int year)
: m_Year(year), m_Color(color) {}

virtual const string getDesc() const {


string str = m_Color;
str += " from "; str += stringify(m_Year);
return str;
}

const string &getColor() const {return m_Color;}


const int getYear() const {return m_Year;}
//...
};

class Car : public Vehicle


{
string m_Model;
int m_Power;
public :
Car(const string &color, const int year, const string &model, const int
power)
: Vehicle(color, year), m_Model(model), m_Power (power) {}

const string &getModel() {return m_Model;}


const int getPower() {return m_Power;}

const string getDesc () const {


string str = m_Model; str += " from "; str += stringify(m_Year);
str += " having "; str += m_Color; str += " color (power: ";
str += stringify(m_Power); str += " kW)";
return str;
}
//...
};

class Truck : public Vehicle {


int m_Tonnage;
public:
Truck(const string &color, const int year, const int tonnage)
: Vehicle(color, year), m_Tonnage (tonnage) {}

const string getDesc () const {


string str = "Truck"; str += " from "; str += stringify(m_Year);
str += " having "; str += " tonnage "; str += stringify(m_Tonnage);
return str; }
//...
};
12
Mihai TOGAN

Deşi există clasa Vehicle, să presupunem că ȋn aplicaţie nu vom instanţia obiecte Vehicle.
Presupunem că cerinţele aplicaţiei permit numai obiecte de tip Car, Truck (sau alte tipuri
concrete de vehicule, dacă mai există).

Cu această ipoteză, la nivelul clasei Vehicle, nu are sens implementarea metodei

virtual const string getDesc()

Ȋn plus, ne dorim ca prin design să blocăm posibilitatea de instanţiere în codul aplicaţiei a


clasei Vehicle (întrucât cerinţele nu permit obiecte care să fie doar Vehicle).

Soluţia : transformarea metodei getDesc într-o metodă virtuală pură


class Vehicle {
protected:
int m_Year;
string m_Color;

public :
Vehicle( const string &color, const int year)
: m_Year(year), m_Color(color) {}

virtual const string getDesc() const = 0;

const string &getColor() const {return m_Color;}


const int getYear() const {return m_Year;}
//...
};

Folosind declaraţia de mai sus:


 Metoda getDesc este metodă virtuală pură. Ea nu are implementare la nivelul clasei
Vehicle. Declaraţia de mai sus obligă implementarea metodei la nivelul claselor derivate
care urmează a fi instanţiate (Car, Truck, etc.)

 Clasa Vehicle devine clasă abstractă: clasa nu poate fi instanţiată. Se poate lucra prin
intermediul ei doar cu obiecte ale unor clase concrete moştenite din Vehicle (Car, Truck).

void main ()
{
Vehicle V ("Black", 2006);
//...
};

Conduce la eroare de compilare:


error C2259: 'Vehicle' : cannot instantiate abstract class
due to following members:
'const std::string Vehicle::getDesc(void) const' : is abstract

13
Mihai TOGAN
Folosind acest design la nivelul claselor, cu metodele clasei abstracte din cadrul clasei
Vehicle se poate lucra numai pe obiecte ale unor clase concrete derivate din Vehicle:
void main ()
{
//Vehicle V("Black", 2006);

Vehicle *pVehicle;
bool flag;
//...
cin >> flag;
if (flag == true)
pVehicle = new Car ("Black", 2006, "Toyota Avensis", 100);
else
pVehicle = new Truck ("Green", 2009, 30);

cout << pVehicle->getDesc().c_str() << endl;

//...
Car C ("White", 2008, "Dacia Logan", 85);
Vehicle &rV = C;
rV.getDesc ();
cout << rV.getColor().c_str();
//...
}

Câteva aspecte privind clasele abstracte:


 Definiție 1: o clasă abstractă este o clasă care nu poate fi instanţiată.
 Definiție 2: o clasă abstractă este o clasă care are cel puţin o metodă virtuală pură
(metoda nu permite implementare la nivelul clasei).

Observaţie: o metodă virtuală pură trebuie sa fie obligatoriu virtuală.

O clasă abstractă este folosită numai ca infrastructură pentru a permite apoi definirea
(prin derivare) a unor sub-clase concrete, sub-clase care au însă ceva ȋn comun.

Observaţie: se poate lucra cu instanţe (obiecte) ale sub-claselor folosind pointeri sau
referinţe declarate de tip clasa de bază abstractă.

 O clasă abstractă poate avea una sau mai multe metode virtuale pure.

 Dacă o clasă este derivată dintr-o clasă abstractă, ea poate ramâne ȋn continuare abstractă
daca nu implementează toate metodele virtuale pure moştenite din clasa de bază.

 Definiție: dacă toate metodele sunt virtuale pure, clasa abstractă se numeşte clasă
abstractă pură. O astfel de clasă este considerată o clasă interfaţă (interface) pentru clasele
concrete derivate din ea.

14
Mihai TOGAN
Conceptul de clasă interfaţă (interface class)

 Este un concept OOP foarte clar delimitat la nivelul unora din limbajele OOP existente
(Java, C#).
 La nivelul limbajului C++ nu există o definiție clară, separată pentru o clasă de tip
interfaţă. O clasă abstractă având toate metodele virtuale pure poate fi considerată o
interfaţă.

Notă: Interfaţa este un concept OOP și el poate fi gestionat ȋn C++ folosind mecanismele
tehnice puse la dispozitie de limbaj prin intermediul claselor abstracte.

Conform [stackoverflow.com]: The interface were primarily made popular by Java. Below
are the nature of interface and its C++ equivalents:

1. In Java, the interface can contain only body-less abstract methods;


The C++ equivalent is pure virtual methods, though they can/cannot have body

2. In Java, the interface can contain only static final data members;
The C++ equivalent is static const data members which are compile time constants

3. Multiple interface can be implemented by a Java class, this facility is needed because a
Java class can inherit only 1 class.
The C++ supports multiple inheritance straight away with help of virtual keyword when
needed.

15
Mihai TOGAN
Exemplul 1
class DrawableObject //interface class
{
public:
//draw to GraphicalDrawingBoard
virtual void Draw(GraphicalDrawingBoard&) const = 0;
};

class Triangle : public DrawableObject


{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a triangle
};

class Rectangle : public DrawableObject


{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a rectangle
};

class Circle : public DrawableObject


{
public:
void Draw(GraphicalDrawingBoard&) const; //draw a circle
};

typedef std::list<DrawableObject*> DrawableList;

void main ()
{
DrawableList drawableList;
GraphicalDrawingBoard drawingBoard;

drawableList.pushback(new Triangle());
drawableList.pushback(new Rectangle());
drawableList.pushback(new Circle());

for(DrawableList::const_iterator iter = drawableList.begin(),


endIter = drawableList.end();
iter != endIter;
++iter)
{
DrawableObject *object = *iter;
object->Draw(drawingBoard);
}
}

Observaţii:
 Obiectele construite pot interfaţa cu restul aplicaţiei numai prin metodele expuse de
interfaţă.
 Aplicaţia care lucreză cu aceste obiecte nu „cunoaşte” detaliile de implementare sau alte
informaţii specifice claselor care implementează obiectele respective, ci doar o clasă care
expune interfaţa de lucru.

16
Mihai TOGAN
Exemplul 2: refacem exemplul cu clasele Vehicle, astfel încât :
 să generăm o clasă de interfaţă (abstractă) pentru orice tip de Vehicle;
 să generam o clasa de tip Factory pentru a construi diverse tipuri de Vehicle;

Pasul 1: Generăm un proiect pentru a genera o bibliotecă de tip static/dynamic library


(vehicle.lib + vehicle.dll). Proiectul va conţine următoarele fişiere:

ivehicle.h (singurul fişier header care va fi expus către alte aplicaţii)


_ivehicle_factory.cpp
_ivehicle_car_truck.h
_ivehicle_car_truck.cpp

[ivehicle.h]
pragma once
#include <iostream>

using namespace std;

/*********************************************************************/

//clasa interfata ȋn aplicatii pentru orice tip de vehicol


class Vehicle
{
public :

virtual const string getDesc() const = 0;


virtual const string &getColor() const = 0;
virtual const int getYear() const = 0;
};

// clasa factory (helper) de creare de obiecte vehicol ȋn aplicatii


class Factory_Vehicle
{
public :
static Vehicle* Create_CARInstance (const string &color,
const int year, const string &model, const int power);

static Vehicle* Create_TRUCKInstance (const string &color,


const int year, const int tonnage);
};
/**********************************************************************/

17
Mihai TOGAN
[_ivehicle_factory.cpp]
#include <iostream>
#include "_ivehicle_car_truck.h"

using namespace std;


/****************************************************************************/
string stringify (int value)
{
char pvalue[100];
sprintf_s (pvalue, sizeof (pvalue), "%d", value);
return string (pvalue);
}

/****************************************************************************/
Vehicle* Factory_Vehicle::Create_CARInstance (const string &color,
const int year, const string &model, const int power)
{
return new _Car (color, year, model, power);
}

Vehicle* Factory_Vehicle::Create_TRUCKInstance (const string &color,


const int year, const int tonnage)
{
return new _Truck (color, year, tonnage);
}

/****************************************************************************/

18
Mihai TOGAN
[_ivehicle_car_truck.h]
#pragma once
#include <iostream>

using namespace std;

/****************************************************************************/
class _Vehicle : public Vehicle
{
protected:
int m_Year;
string m_Color;
public :
_Vehicle( const string &color, const int year);

virtual const string getDesc() const = 0;


const string &getColor() const;
const int getYear() const;
};

/****************************************************************************/
class _Car : public _Vehicle
{
string m_Model;
int m_Power;
public :
_Car(const string &color, const int year,
const string &model, const int power);

const string getDesc () const;


const string &getModel();
const int getPower();
};

/****************************************************************************/
class _Truck : public _Vehicle
{
int m_Tonnage;
public:
_Truck(const string &color, const int year, const int tonnage);

const string getDesc () const;


};

/****************************************************************************/

19
Mihai TOGAN
[_ivehicle_car_truck.cpp]
#pragma once
#include <iostream>
#include "_ivehicle_car_truck.h"
using namespace std;

string stringify (int value);

/****************************************************************************/
_Vehicle::_Vehicle (const string &color, const int year)
: m_Year(year), m_Color(color) {}

const string& _Vehicle::getColor() const {


return m_Color;
}
const int _Vehicle::getYear() const {
return m_Year;
}

/****************************************************************************/
_Car::_Car(const string &color, const int year,
const string &model, const int power)
: _Vehicle(color, year), m_Model(model), m_Power (power) {}

const string& _Car::getModel() {


return m_Model;
}

const int _Car::getPower() {


return m_Power;
}

const string _Car::getDesc () const {


string str = m_Model;
str += " from "; str += stringify(m_Year);
str += " having "; str += m_Color; str += " color (power: ";
str += stringify(m_Power); str += " kW)";
return str;
}
/****************************************************************************/
_Truck::_Truck(const string &color, const int year, const int tonnage)
: _Vehicle(color, year), m_Tonnage (tonnage) {}

const string _Truck::getDesc () const {


string str = "Truck"; str += " from "; str += stringify(m_Year);
str += " having "; str += " tonnage "; str += stringify(m_Tonnage);
return str;
}
/****************************************************************************/

Ȋn final, realizăm build-ul pentru a genera biblioteca vehicle (vehicle.lib + vehicle.dll)

20
Mihai TOGAN
Pasul 2
Construim o aplicaţie care foloseşte biblioteca și instanţiază diverse obiecte de tip Vehicle:

[main.cpp]
#include <iostream>
#include "ivehicle.h"

using namespace std;


void main ()
{

Vehicle *pVehicle;
bool flag;
//...
cin >> flag;
if (flag == true)
pVehicle = Factory_Vehicle::Create_CARInstance("Black", 2006,
"Toyota Avensis", 100);
else
pVehicle = Factory_Vehicle::Create_TRUCKInstance("Green",
2009, 30);

cout << pVehicle->getDesc().c_str() << endl;

//...
cin >> flag;
if (flag == true)
pVehicle = Factory_Vehicle::Create_CARInstance ("Black", 2006,
"Toyota Avensis", 100);
else
pVehicle = Factory_Vehicle::Create_TRUCKInstance ("Green",
2009, 30);

cout << pVehicle->getDesc().c_str() << endl;

//...
Vehicle &rV = *pVehicle;
rV.getDesc ();
cout << rV.getColor().c_str();
}

Observaţii:
 Aplicaţia foloseşte pentru compilare doar declaraţiile clasei Vehicle și Factory_Vahicle
(#include "ivehicle.h")

 Aplicaţia linkează cu biblioteca vehicle.lib


21

You might also like