Le classi Definizione di classe Attributi e metodi di una classe Costruttori e distruttori Private e public Funzioni friend Il puntatore this 1 Cos’è un oggetto? • Né più né meno di quello che potreste trovare scritto in un vocabolario… – Un oggetto è un’entità che si possa immaginare dotata di determinate caratteristiche e funzionalità. • Lo stato di un oggetto è rappresentato da dati che ne descrivono le caratteristiche in un certo istante • Le funzionalità di un oggetto sono le operazioni che può svolgere quando glielo si richiede (cioè quando riceve un messaggio) • Nella nostra vita quotidiana siamo molto più abituati a ragionare per oggetti che non in modo strutturato! 2 Un esempio... 3 … cos’è un oggetto: Un insieme di dati e funzioni: Dato Dato Dato 4 Incapsulazione • Netta divisione fra interfaccia e implementazione • Da fuori si vede solo l’interfaccia che definisce i messaggi accettati dall’oggetto • I dettagli dell’implementazione (dati e codice delle funzioni) sono invisibili dall’esterno • Ogni oggetto ha in se tutto ciò che gli serve per rispondere alle chiamate (o deve sapere a chi chiedere…) • Il confinamento di informazioni e funzionalità in oggetti permette livelli maggiori di astrazione e semplifica la gestione di sistemi complessi. 5 Approccio OO • Sono le strutture di dati che svolgono le azioni, non le subroutines • Il lavoro è svolto dal server, non dal client • “Cos’ è?” “Com’ è fatto?” Data Oriented • “Cosa può fare per me?” Object Oriented 6 Perché programmare per oggetti? • Programmare per oggetti non velocizza l’esecuzione dei programmi... • Programmare per oggetti non ottimizza l’uso della memoria... E allora perchè programmare per oggetti? • Programmare per oggetti facilita la progettazione e il mantenimento di sistemi software molto complessi! 7 Caratteristiche del software non mantenibile • Rigidità – non può essere cambiato con faciltà – non può essere stimato l’impatto di una modifica • Fragilità – una modifica singola causa una cascata di modifiche successive – i bachi sorgono in aree concettualmente separate dalle aree dove sono avvenute le modifiche • Non riusabilità – esistono molte interdipendenze, quindi non è possibile estrarre parti che potrebbero essere comuni 8 Programmazione ad oggetti • La programmazione ad oggetti, attraverso l’incapsulazione, consente di: – ridurre la dipendenza del codice di alto livello dalla rappresentazione dei dati – riutilizzare del codice di alto livello – sviluppare moduli indipendenti l’uno dall’altro – avere codice utente che dipende dalle interfacce ma non dall’implementazione 9 Organizzazione dei files • Normalmente, le dichiarazioni delle interfacce e le specifiche sono separate dall’implementazione – header files (.h o .hh) • inclusi nei file sorgente utilizzando direttive del precompilatore #include <iostream> • non contengono codice eseguibile (con l’eccezione delle definizioni delle funzioni inline) • non devono essere inclusi piu` di una volta, per evitare problemi con il linker #ifndef MyHeader_H #define MyHeader_H // dichiarazioni ….. #endif 10 Organizzazione dei files (2) – Files sorgente (.C,.cxx,.cpp,.cc) • contengono l’implementazione di funzioni e metodi • codice eseguibile • includono gli header files utilizzando le direttive del preprocessore • vengono compilati 11 C++ e Object Orientation • Definizione di nuovi tipi (oltre a int, float, double) come: • numeri complessi, • vettori, • matrici, . . . • ma anche: • curve, • superfici, • Modelli 3D,... • Gli oggetti permettono di modellare una problema che rappresenti la realtà 12 …C++ e Object Orientation • Object Orientation implementata in C++ attraverso il concetto di classe: • I dati privati (o attributi) di una classe definiscono lo stato dell’oggetto • Le funzioni (o metodi) di una classe implementano la risposta ai messaggi 13 Una classe C++ Attributo Attributo Attributo 14 Classe Vector2D • Un esempio: un vettore bidimensionale Vector2D.h costruttore class Vector2D { public: Vector2D(double x, double y); double x(); double y(); Vector2D.cc double r(); double phi(); #include “Vector2D.h” private: #include <math.h> double x_; Vector2D::Vector2D(double x, double y): x_(x), double y_ y_(y) { }; } double Vector2D::x() { return x_; } funzioni o metodi dati o attributi Punto e virgola! double Vector2D::r() { return sqrt( x_*x_ + y_*y_); } ... 15 Interfaccia e implementazione • Gli attributi privati non sono accessibili al di fuori della classe Vector2D.cc • I metodi pubblici sono #include “Vector.h” gli unici visibili Vector2D.h class Vector2D { public: Vector2D(double x, double y); double x(); double y(); double r(); double phi(); private: double x_; double y_; }; Vector2D::Vector2D(double x, double y) : x_(x), y_(y) {} double Vector2D::x() { return x_; } double Vector2D::r() { return sqrt(x_*x_ + y_*y_); } 16 Costruttori e distruttori • Un costruttore è un metodo il cui nome è quello della classe a cui appartiene • Lo scopo di un costruttore è quello di costruire oggetti del tipo della classe. Questo implica l’inizializzazione degli attributi e, frequentemente, l’allocazione della memoria necessaria • Un costruttore la cui lista di argomenti è vuota o composta di argomenti di default viene normalmente chiamato costruttore di default Vector2D::Vector2D() {. . . .} // costruttore di default #include “Vector2D.h” . . . Vector2D v; // oggetto costruito con il // costruttore di default 17 Costruttori e distruttori (2) • Un costruttore del tipo che ha come argomento un riferimento ad un oggetto della stessa classe viene chiamato copy constructor (costruttore per copia) Vector2D::Vector2D(const Vector2D& v) {. . . .} Vector2D v(v1); // dove v1 e` di tipo Vector2D • Il copy constructor viene normalmente utilizzato: – quando un oggetto è inizializzato per assegnazione – quando un oggetto è passato come argomento ad una funzione – quando un oggetto è ritornato da una funzione • Se non viene fornito esplicitamente dall’utente, il compilatore ne genererà uno automaticamente 18 Costruttori e distruttori (3) • Gli attributi di una classe possono essere inizializzati nel costruttore per mezzo di una lista di inizializzatori, che precede il corpo della funzione Vector2D::Vector2D(double x, double y) : x_(x), y_(y) { . . . } • Quando uno degli attributi è esso stesso una classe, il costruttore appropriato viene scelto sulla base dei parametri forniti nell’inizializzazione • E` obbligatorio inizializzare gli attributi (non statici) che siano o riferimenti o const 19 Costruttori e distruttori (4) • Il distruttore è un metodo il cui nome è quello della classe a cui appartiene preceduto da una tilde (~) • Il distruttore viene chiamato automaticamente quando un oggetto sta per essere distrutto (sia perchè delete è stato invocato sia perchè l’oggetto è finito fuori scope • Il compito del distruttore è di assicurarsi che l’oggetto per cui è invocato verrà distrutto senza conseguenze. In particolare, se memoria è stata allocata nel costruttore, il distruttore dovrà assicurarsi di restituirla allo heap Vector2D::~Vector2D() {} // vuoto, in questo caso 20 Costruttori e distruttori (5) • I costruttori con un solo parametro sono automaticamente trattati come operatori di conversione Vector2D::Vector2D(int i) {. . .} // costruisce un vettore a partire da un intero, ma puo` // essere usato per convertire un intero in vettore v=Vector2D(i); • Per evitare la conversione si puo` usare explicit explicit Vector2D(int); // solo costruttore 21 Classe Vector2D • Come usare Vector2D: main.cc #include <iostream> using namespace std; #include “Vector2D.h” invoca il constructor int main() { Vector2D v(1, 1); cout << “ v = << v.x() << v.y() cout << “ r = cout << “ phi return 0; } (“ << “,” << “)” << endl; “ << v.r(); = “ << v.phi() << endl; Output: v = (1, 1) r = 1.4141 phi = 0.7854 22 Classe Vector2D • … oppure attraverso un puntatore... main.cc #include <iostream> using namespace std #include “Vector2D.h” Allocazione sullo heap int main() { Vector2D *v = new Vector2D(1, 1); cout << “ v = (“ << v->x() << “,” << v->y() << “)” << endl; cout << “ r = “ << v->r(); cout << “ phi = “ << v->phi() << endl; delete v; return 0; } Attenzione! Output: v = (1, 1) r = 1.4141 phi = 0.7854 23 Interfaccia e implementazione • La struttura interna dei dati (x_, y_) che rappresentano l’oggetto della classe Vector2D sono nascosti (private) agli utilizzatori della classe. • Gli utilizzatori non dipendono dalla struttura interna dei dati • Se la struttura interna cambia (es.: r_, phi_), il codice che usa Vector2D non deve essere modificato. 24 Classe Vector2D • Protezione dell’accesso ai dati: main.cc #include <iostream> using namespace std #include “Vector2D.h” int main() { Vector2D v(1, 1); cout << << << cout << cout << “ V = (“ v.x_ << “,” // v.y_ << “,” << endl; // non compila ! “ r = “ << v.r(); “ phi = “ << v.phi() << endl; } • I metodi di una classe hanno libero accesso ai dati privati e protetti di quella classe 25 Selettori e modificatori • Selettore: metodo che non modifica lo stato (attributi) della classe. E’ dichiarato const • Modificatore: metodo che può modificare lo stato della classe Vector2D.cc Vector2D.h class Vector2D #include “Vector2D.h” { public: void Vector2D::scale(double Vector2D(double x, double y); { double x() const; x_ *= s; y_ *= s; double y() const; } double r() const; Selettori (const) double phi() const; main.cc void scale(double s); private: #include “Vector2D.h” double x_, y_; modificatore int main() }; { const Vector2D v(1, double r = v.r() // v.scale( 1.1 ); // } s) 0); OK errore! 26 friend • La keyword friend puo` essere usata perche` una funzione (o una classe) abbia libero accesso ai dati privati di un’altra classe class A { . . . friend int aFunc(); friend void C::f(int); }; class B { … friend class C; }; class C { . . . }; 27 friend (2) • friend (nonostante il nome) e` nemico dell’incapsulamento e quindi dell’Object Orientation • Un uso eccessivo di friend è quasi sempre sintomo di un cattivo disegno • Esistono anche situazioni in cui un friend può essere accettabile – Overloading di operatori binari – Considerazioni di efficienza – Relazione speciale fra due classi “A programmer must confer with an architect before making friend declarations” 28 this • In una classe è automaticamente definito un attributo particolare: this – this è un puntatore all’oggetto di cui fa parte – E’ particolarmente utile quando una funzione deve restituire l’oggetto tramite il quale è stata invocata Vector2D.h Vector2D.cc class Vector2D { public: Vector2D& copia(const Vector2D& ); // ... private: double x_, y_; }; Vector2D& copia(const Vector2D& v){ x_=v.x(); y_=v.y(); return *this; } L’operatore copia ritorna una referenza a se stesso. Permette copie multiple main.cc #include “Vector2D.h” int main() { Vector2D null(0, 0); Vector2D a, b; a.copia(b.copia(null)); } 29 static • Attributi dichiarati static in una classe sono condivisi da tutti gli oggetti di quella classe • Metodi dichiarati static non possono accedere ad attributo non statici della classe • Attiributi statici possono essere usati e modificati soltanto da metodi statici • Nonostante l’utilizzo di static sembri imporre condizioni troppo restrittive, esso risulta utile nell’implementazione di: – contatori – singleton (vedi oltre) 30 Un contatore Class MyClass { private: static int counter; static void increment_counter() { counter++; } static void decrement_counter() { counter--; } public: MyClass() { increment_counter(); } ~MyClass() { decrement_counter(); } static int HowMany() { return counter; } }; Un membro statico deve essere #include <iostream> inizializzato una e una sola volta using namespace std #include “MyClass.h” nel codice eseguibile int MyClass::counter=0; Un metodo statico puo` essere invocato cosi`... int main() { MyClass a,b,c; MyClass *p=new MyClass; cout<<“ How many? “<< MyClass::HowMany() <<endl; delete p; cout<<“ and now? “<< a.HowMany() <<endl; return 0; } … o cosi`... 31 Un singleton • Un singleton è una classe di cui, in ogni momento nel corso del programma, non può esistere più di una copia (istanza) class aSingleton { Pattern utile per private: static aSingleton *ptr; l’implementazione di aSingleton () {} classi “manager” di cui public: static aSingleton *GetPointer(){ deve esistere una sola if (ptr==0) istanza ptr=new aSingleton; return ptr; #include “aSingleton.h” } }; aSingleton *aSingleton::ptr=0; Attenzione a non farlo diventare l’equivalente di un common block! int main() { aSingleton *mySing= aSingleton::GetPointer(); . . . Return 0; } 32