Introduction to Object-Oriented Programming in C++ Vincenzo Innocente CERN, Geneva, Switzerland Vincenzo Innocente 1 Programmazione procedurale • Uno dei principali problemi del software è la sua evoluzione e la sua manutenzione – specialmente in grossi progetti come un esperimento di HEP • Esempi con linguaggi procedurali (Fortran): – Cosa succede quando il codice viene modificato – Dipendenze all’interno del codice Vincenzo Innocente 2 C++/Object Oriented • Riduce la dipendenza del codice di alto livello dalla rappresentazione dei dati Permette il riutilizzo del codice di alto livello • Nasconde i dettagli di implementazione Vincenzo Innocente 3 Classi e oggetti • Definizione di nuovi tipi (oltre a int, float, double) come: – numeri complessi, – vettori, – matrici, . . . • ma anche: – – – – tracce, superfici, elementi di rivelatori, cluster, . . . • Gli oggetti permettono di modellare una problema che rappresenti la realtà Vincenzo Innocente 4 Concetti base dell’OO • • • • Classi ed oggetti Incapsulamento Relazione di ereditarietà Polimorfismo • Programmazione Generica (C++) Vincenzo Innocente 5 Puntatori • Riferimento ad una locazione di memoria #include <iostream.h> int main() { int j = 12; int *ptr = &j; ptr cout << *ptr << endl; j = 24; cout << *ptr << endl; cout << ptr << endl; j 24 12 return 0; } 12 24 0x7b03a928 indirizzo di memoria Vincenzo Innocente 6 Puntatori • Puntatore nullo #include <iostream.h> ptr j 12 int main() { int j = 12; int *ptr = 0; cout << *ptr << endl; // crash ! return 0; } Segmentation violation (core dumped) Vincenzo Innocente 7 Puntatori: allocazione dinamica • Riferimento ad una locazione di memoria #include <iostream.h> int main() { int *ptr = new int; ptr 12 *ptr = 12; cout << *ptr << endl; delete ptr; return 0; } • Attenzione: – Non usare delete fa accumulare locazioni di memoria inutilizzate (memory leak) – Utilizzare puntatori prima del new o dopo il delete causa il crash del programma Vincenzo Innocente 8 Classi e Oggetti • Un esempio di programma “orientato ad oggetti”: un videogioco Vincenzo Innocente 9 Cliente Soldato Vincenzo Innocente 10 Class Vector • An example: un tri-dimentional vector • With respect to a structure, a class contains functions beside data Vector.h class Vector { public: Vector(double x, double y, double z); Vector(Vector& v); double x(); double y(); double z(); double r(); double phi(); double theta(); protected: double _x, _y, _z; }; constructor functions or methods data Vector.cc #include “Vector.h” Vector::Vector(double x, double y, double z) : _x(x), _y(y), _z(z) {} copy constructor Vector::Vector(Vector& v) : _x(v.x()), _y(v.y()), _z(v.z()) {} double Vector::x() { return _x; } double Vector::r() { return sqrt(_x*_x + _y*_y + _z*_z); } Vincenzo Innocente 11 Class Vector • How to use Vector: main.cc #include <iostream.h> #include “Vector.h” invoke the constructor int main() { Vector v(1, 1, 0); cout << << << << cout << cout << “ v = (“ v.x() << “,” v.y() << “,” v.z() << “)” << endl; “ r = “ << v.r(); “ theta = “ << v.theta() << endl; } Output: v = (1, 1, 0) r = 1.4141 theta = 1.5708 Vincenzo Innocente 12 Class Vector • Data access protection: main.cc #include <iostream.h> #include “Vector.h” int main() { Vector v(1, 1, 0); cout << << << << cout << cout << “ V = (“ v._x << “,” // v._y << “,” // does not v._z << “)” << endl; // compile ! “ r = “ << v.r(); “ theta = “ << v.theta() << endl; } Vincenzo Innocente 13 Interface and implementation • Protected data are not accessible outside the class Vector.hh class Vector { public: Vector(double x, double y, double z); double x() const; double y() const; double z() const; double r() const; double phi() const; double theta() const; protected: double _x, _y, _z; }; const Vector.cc #include “Vector.h” Vector::Vector(double x, double y, double z) : _x(x), _y(y), _z(z) {} double Vector::x() const { return _x; } double Vector::r() const { return sqrt(x*x + y*y + z*z); } Vincenzo Innocente 14 Interface and implementation • The internal structure of the data (_x, _y, _z) that represent the objects of the class Vector are hidden (protected) to the clients of the class. • The clients do not depend on the internal structure of the data (as it was the case for clients of FORTRAN common block) • If the internal structure changes (es.: _r, _theta, _phi), the code using Vector does not have to be modified. Vincenzo Innocente 15 Operators • It is possible to redefine +, -, *, [], ++, ==, . . . Vector.hh class Vector { public: Vector(double x, double y, double z); double x() const; double y() const; double z() const; double r() const; double phi() const; double theta() const; Vector operator+(const Vector& v) const; Vector operator-(const Vector& v) const; protected: double _x, _y, _z; }; Vector.cc Vector Vector::operator+(const Vector& v) const { return Vector(_x + v._x, _y + v._y, _z + v._z); } Vector Vector::operator-(const Vector& v) const { return Vector(_x - v._x, _y - v._y, _z - v._z); } Vincenzo Innocente 16 Operators • Example: main.cc #include <iostream.h> #include “Vector.h” int main() { Vector v1(1, 0, 0), v2(0, 1, 0); Vector v; redefinition of << v = v1 + v2; cout << “ v = “ << v << endl; cout << “ r = “ << v.r(); cout << “ theta = “ << v.theta() << endl; } Output: v = (1, 1, 0) r = 1.4141 theta = 1.5708 Vincenzo Innocente 17 Operators • Example: main.cc #include #include #include #include <iostream.h> <math.h> “Vector.h” “Matrix.h” // matrix 3x3 int main() { Vector v1(1, 1, 0); p double phi = M_PI/3; double c = cos(phi), s = sin(phi); Matrix m(1, 0, 0, 0, c, s, 0, -s, c); Vector u = m * v; } Vincenzo Innocente 18 Operator overloading • It is possible to define functions with the same name but different arguments Vector.hh class Vector { public: ... Vector operator*(double s) const; double operator*(const Vector& v) const; protected: double _x, _y, _z; }; Vector.cc Vector Vector::operator*(double s) const { return Vector(_x *s, _y *s, _z *s); } double Vector::operator*(const Vector& v) const { return (_x * v._x + _y * v._y + _z * v._z); } Vincenzo Innocente 19 Static Functions and data • Some functions can be common to the whole class Particle.h class Particle { public: Particle(int code, double mass, int charge); int code() const { return _code; }; double mass() { return _mass; }; int charge() { return _charge; }; static int numberOfParticles(); protected: double _mass; int _code, _charge; static int _n; }; Particle.cc #include “Particle.h” int Particle::_n = 0; Particle::Particle(int code, double mass, int charge) : _code(code), _mass(mass), _charge(charge) { _n ++; } int Particle::numberOfParticles() { return _n; } Vincenzo Innocente 20 Classi astratte • Esempio classico: Shape • Tutti oggetti nella finestra hanno comportamenti comuni che possono essere considerati in astratto: – disegna, sposta, ingrandisci, etc. Vincenzo Innocente 21 Ereditarietà e riuso del codice CenteredShape.h Class CenteredShape: public Shape { public: CenteredShape( Point2d c ) : center_( c ) { /*draw();*/ } ~Circle() { /*cancel();*/ } Non si possono chiamare metodi virtuali in costruttori e distruttori (troppo presto, troppo tardi) void moveAt( const Point2d& ); void moveBy( const Vector2d& ); virtual void scale( double ) = Square.h 0; virtual void rotate( double ) = #include “CenteredShape.hh” 0; virtual void draw() const class = 0; Square : public CenteredShape virtual void cancel() const { = 0; public: Square( Point2d lowerCorner, Point2d protected: upperCorner ) : Point2d center_; CenteredShape( median(lowerCorner, }; upperCorner) ), touc_(upperCorner - center_) { draw(); } ~Square() { cancel(); } virtual void scale( double s ) { cancel(); centerToUpperCorner_ *= s; draw(); } virtual void rotate( double phi ); virtual void draw() const; virtual void cancel() const; private: Vector2d touc_; }; Vincenzo Innocente 22 Quadrato Square.cc Square.h #include “Square.h” class Square { public: Square(const Point2d&, const Point2d&); ~Square(); void moveAt( const Point2d& p ); void moveBy( const Point2d& p ); void scale( double s ); void rotate( double phi ); void draw() const; void cancel() const; private: Point2d center_; Vector2d centerToUpperCorner_; }; void Square::rotate( double phi ) { cancel(); centerToUpperCorner_.rotate( phi ); draw(); } centerToUpperCornerupperCorner _ loweCorner void Square::draw() const { float x[4], y[4]; Vector2d delta( centerToUpperCorner_ ); for ( int i = 0; i < 4; i++ ) { Point2d corner = center_ + delta; x[i] = corner.x(); y[i] = corner.y(); delta.rotate( M_PI_2 ); } polyline_draw(x, y, 4); } Square::Square(const Point2d& lowerCorner, const Point2d& upperCorner ) : center_( median(lowerCorner, upperCorner) ), centerToUpperCorner_( upperCorner - center_ ) { draw(); } void Square::scale( double s ) { cancel(); centerToUpperCorner_ *= s; draw(); } Vincenzo Innocente 23 Cerchio Nome della classe Circle.h Circle.cc Costruttore class Circle { public: #include “Circle.h” Circle(Point2d center, double radius); ~Circle(); void Circle::draw() const { void moveAt(const Point2d & p); const int numberOfPoints = 100; void moveBy(const Point2d & p); float x[numberOfPoints], void scale(double s); Point2d: classe che rappresenta y[numberOfPoints]; void rotate(double phi); float phi = 0, deltaPhi = 2*M_PI/100; un punto in 2 dimensioni. void draw() const; for ( int i = 0; i < numberOfPoints; void cancel() const; ++i ) { private: x[i] = center_.x() + radius_ * Point2d center_; cos( phi ); }; double radius_; y[i] = center_.y() + radius_ * sin( phi ); phi += dphi; } polyline_draw(x, y, numberOfPoints ); } Distrutt ore Main.cc Interfaccia Pubblica Metodi: operazioni sugli oggetti void Circle::moveAt( const #include “Circle.h” int main() { { cancel(); center_ = p; Circle c( Point2d(10, } “Dati” privati 10), 5 ); c.draw(); c.moveAt(Point2d(20, 30)); Punto e virgola! return 0; } void Circle::scale( double (Attributi, { membri) cancel(); radius_ *= s; Point2d& p ) draw(); s ) draw(); } Circle::Circle( Point2d c, double r ) : center_( c ), radius_( r ) { draw(); } Circle::~Circle() { cancel(); } Vincenzo Innocente 24 Codice Applicativo (Client) Main.cc #include “Circle.h” #include “Square.h” int main() { Circle c1( Point2d(2.,3.), 4.23, ); Square r1( Point2d(2.,1.), Point2d(4.,3.) ); Circle * circles[ 10 ]; for ( int i = 0; i < 10; ++i ) { circles[ i ] = new Circle( Point2d(i,i), 2. ); } Costruisce un vettore di puntatori a cerchi, crea oggetti in memoria e salva i loro puntatori nel vettore. for ( int i = 0; i < 10; ++i ) circles[ i ]->draw(); return 0; } Itera sul vettore e invoca draw() per ogni elemento Come gestire cerchi e quadrati insieme? Vincenzo Innocente 25 Programmazione generica Funzioni Generiche “<” deve essere definito per “T” template<class T> inline T min(const T& a, const T& b) { return a < b ? a:b; } Main.cc P.h Usa min<float> class P { public: P(int ix,int iy):x(ix),y(iy){} int main() { float a = min(3.,2.); int i = min(2,3); bool operator<(const P& b) const { return y<b.y ||( y==b.y && x<b.x ); } private: float x; float y; }; P p1(1.,2.), p2(3.,1.); P p3 = min(p1,p2); return 0; } Vincenzo Innocente Usa min<P> 26 Programmazione generica Classi Generiche “copy” è una funzione generica di STL Vector3d.h template <class T> class Vector3d { public: Vector3d(const T*a) { copy(a,a+3,&x[0]); } Vector3d<T>& operator+=(const Vector3d<T>& b) {x[0]+=b.x[0]; x[1]+=b.x[1]; x[2]+=b.x[2]; return *this;} private: T x[3]; }; Ritorna se stesso per permettere la concatenazione di operatori Vincenzo Innocente Main.cc #include “vector3d.h” int main() { Vector3d<float> a, b,c; c+=b+=a; Vector3d<complex> c1, c2; c2+=c1; return 0; } 27 La libreria standard: std (STL) • Contenitori generici: – vector, list, set, map, “string” • Iteratori – puntatori “intelligenti” nei contenitori generici • Funzioni generiche – copy, merge, find, sort, swap,… – agiscono su iteratori Vincenzo Innocente 28 Un semplice vettore Main.cc #include <vector> int main() { vector<int> v; di interi vuoto cout << v.size() << endl; di v: zero for ( int i = 0; i != 10; ++i ) v.push_back( i ); intero in coda a v // creamo un vettore // stampa dimensione // un loop da 0 a 9 // aggiungiamo un cout << v.size() << endl; // stampa dimensione di v: 10 // creiamo un iteratore costante per un vettore di interi: // p si comporta come un “const int *” a tutti gli effetti vector<int>::const_iterator p; for ( p = v.begin(); p != v.end(); ++p ) // “itera dall’inizio alla fine di v” cout << (*p) << “ “; cout << endl; return 0; } Vincenzo Innocente 29 Un semplice vettore begin() end() 0 1 2 p p p ... 9 p Vincenzo Innocente p p 30 Vettore di classe astratta Shape* Main.cc #include “Circle.h” #include “Square.h” #include <vector> int main() { vector<Shape *> vs; // un vettore di puntatori a Shapes for ( int i = 0; i != 10; ++i ) { // aggiungiamo un puntatore ad un nuovo cerchio vs.push_back(new Circle(Point2d(i, i), 2.)); // aggiungiamo un puntatore ad un nuovo quadrato vs.push_back(new Square(Point2d(i, i), Point2d(i + 1, i + 2))); } // iteratore: essenzialmente un puntatore a Shape* ossia un Shape** vector<Shape *>::const_iterator p; for ( p = vs.begin(); p != vs.end(); ++p ) // loop sull’intero vettore (*p)->draw(); // disegniamo ogni Shape return 0; } Vincenzo Innocente 31 Interfaccia astratta Shape.h class Shape { public: Shape() { } virtual ~Shape() { } virtual ) = 0; virtual virtual 0; virtual virtual }; Main.cc void moveAt( const Point2d& #include “Circle.h” void scale( double s ) = 0; #include “Square.h” void rotate( double phi ) = int main() void draw() const = 0; { void cancel() const = 0; Shape * shapes[ 10 ]; int index = 0; for ( int i = 0; i < 10; i++ ) { Shape * s; s = new Circle( Point2d(i, i), 2.) ); shapes[ index ++ ] = s; s = new Square( Point2d(i, i), Point2d(i+1, i+2)) ); shapes[ index ++ ] = s; } Interfaccia di metodi puramente virtuali Square.h for ( int i = 0; i < 10; i++ ) shapes[ i ]->draw(); return 0; } #include “Shape.h” class Square : public Shape { // …. Il resto tutto uguale a prima }; Vincenzo Innocente 32 Superfici e traiettorie • Nel tracking spesso è necessario calcolare intersezioni tra curve (tracce) e superfici (elementi di detector) Intersection Trajectory Surface Line Plane PolyLine Cylinder Helix Vincenzo Innocente 33 Superfici e traiettorie • Interfaccia delle diverse Trajectory Trajectory.h Line.h class Trajectory { public: virtual Point position(double s) = 0; virtual Vector direction(double s) = 0; }; #include “Trajectory.h” class Line : public Trajectory { public: virtual Point position(double s); virtual Vector direction(double s); public: Point origin_; Vector direction_; }; Helix.h #include “Trajectory.h” class Helix : public Trajectory { public: virtual Point position(double s); virtual Vector direction(double s); }; Vincenzo Innocente 34 Superfici e traiettorie • Implementazione Trajectory.cc #include “Trajectory.h” Helix.cc // … vuoto ... #include “Helix.h” Helix::Helix() { } Line.cc #include “Line.h” Line::Line(const Point& o, constVector& d) : origin_( o ), direction_( d.unit() ) { } Point Helix::position(double s) { // implementazione } Point Line::position(double s) { return ( origin_ + s * direction_ ); } Vincenzo Innocente 35 Superfici e traiettorie • Interfaccia delle varie Surface Surface.h distanza (con segno) di un punto dalla superficie class Surface { public: virtual distance(const Point& p) = 0; virtual derDist(const Point& p, const Vector& r) = 0; }; Plane.h #include “Surface.h” class Plane : public Surface { public: virtual distance(const Point& p); virtual derDist(const Point& p, const Vector& r); protected: Point origin_; Vector norm_; double dist_; }; Cylinder.h #include “Surface.h” class Cylinder : public Surface { public: virtual distance(const Point& p); virtual derDist(const Point& p, const Vector& r); }; Vincenzo Innocente 36 Superfici e traiettorie • Surface è una classe astratta Plane.cc #include “Plane.h” Surface.cc #include “Surface.h” // vuoto Plane::distance(const Point& p) { return ( _dist - ( (p - origin_) * direction_) ); } Plane::derDist(const Point& p, const Vector& r) { Cylinder.cc return - r * _direction; } #include “Cylinder.h” Cylinder::distance(const Point& p) { /* . . . */ } Cylinder::derDist(const Point& p, const Vector& r) { /* . . . */ } Vincenzo Innocente 37 Superfici e traiettorie • Interfaccia di Intersection Intersection.h forward class declaration class Surface; class Trajectory; class Intersection { public: Intersection(Surface* s, Trajectory* t) surface_(s), trajectory_(t) {} Point intersect(double s1, double s2); protected: double sIntersect(double s1, double s2); Surface* surface_; Trajectory* trajectory_; }; Vincenzo Innocente 38 Superfici e traiettorie Intersection.cc #include #include #include #include • Implementazion e dell’algoritmo “Intersection.h” <math.h> “Surface.h” “Trajectory.h” const int maxIterations 20 const double sMax 1.e+6 const double accuracy1.e-3 // controlla che test è tra s1 double Intersection::sIntersect(double s1, double s2) { // algoritmo di Newton-Raphson double s = s1; double maxS = max(s1, s2); double minS = min(s1, s2); double d, delta; for( int j = 0; j < maxIterations; j++ ) { Point p = _trajectory>position( s ); d = surface_->distance( p ); delta = surface_->derDist( p, trajectory_>direction( s ) ); double ds = - d / delta; double test = s + ds; e s2 if( (s1 - test) * (test - s2) < 0.0 ) { if ( s1 < s2 ) s += abs( d ); else s -= abs( d ); if( s > maxS || s < minS ) return sMax; } else s = test; if( abs(d) < accuracy ) return s; } return sMax; } Point Intersection::intersect(double s1, double s2) { return trajectory_>position(sIntersect(s1, s2)); } Vincenzo Innocente 39 Superfici e traiettorie • Intersection usa solo: – I metodi position e direction di un’oggetto Trajectory – I metodi distance e derDist di un oggetto Surface • E’ possibile aggiungere una nuova classe che modellizza una nuova Trajectory o una nuova Surface e Intersection continua a funzionare senza modificare una linea di codice! • E’ possibile rendere anche Intersection astratto... Vincenzo Innocente 40 “Open/Closed principle” • Un buon codice deve essere – aperto ad estensioni – chiuso a modifiche • Modificare un codice funzionante può introdurre bachi… • L’Object Oriented, con il meccanismo delle classi virtuali, permette di applicare questo principio Vincenzo Innocente 41 Conclusioni • La programmazione C++ Object Oriented può aiutare a ridurre le dipendenze all’interno del codice e quindi lo sviluppo del programma ... • … ma va utilizzato in maniera adeguata, disegnando il codice prima di implementarlo • è facile scrivere un codice C++ traslitterando un codice F77, ma questo non produce grandi miglioramenti Vincenzo Innocente 42 Unified Modeling Language • Class Diagrams • Sequence and Collaboration Diagrams • Use Case Diagrams • State Diagrams Vincenzo Innocente 43 UML Model of “Shape” Vincenzo Innocente 44 Class Diagram Vincenzo Innocente 45 Class Diagram Vincenzo Innocente 46 Object Sequence Diagram Vincenzo Innocente 47 Object Collaboration Diagram Vincenzo Innocente 48 Design Patterns E. Gamma et al., Design Patterns “Elementi di software OO riutilizzabile” • Piccoli insiemi di classi che collaborano implementando dei comportamenti tipici – Creational patterns – Structural patterns – Behavioral patterns Alcuni pattern classici stanno diventanto obsoleti grazie al supporto dei Template Vincenzo Innocente 49 Factory Client I client possono richiedere la creazione di un prodotto senza dipendervi. Factory createProduct1 () : AbstractProduct createProduct2 () : AbstractProduct AbstractProduct La Factory dipende dai prodotti concreti, mentre i client dipendono solo AbstractPro duct. ConcreteProduct1 ConcreteProduct2 Vincenzo Innocente 50 Singleton Singleton _instance : Singleton if (_instance==0) _instance = new Singleton() instance () : Singleton return _instance; specificService () user_code() { Singleton::instance()>specificService(...); } Il Singleton pattern piò essere usato ogni volta che una classe deve essere instanziata una sola volta, e viene usata da diversi oggetti. Per evitare istanziazione accidentale, il constructor deve essere privato. Più istanze, ma in numero ben determinato, possono esistere (multiton) Siccome vengono usate funzioni statiche, l’ereditarietà non può essere applicata. Vincenzo Innocente 51 Template Singleton Singleton T _instance :T if (_instance==0) _instance = new T(); return _instance; instance () : T Singleton{V} V V( ) specificService( ) class V : public Singleton<V> { public: specificService(...); private: V(); friend class Singleton<V>; }; user_code() { V::instance() ->specificService(...); } Un Template Singleton può essere specializzato usando la classe stessa come argomento del template. Se V ha un constructor privato, Singleton<V> deve essere friend di V (non tutti i compilatori lo supportano…). Vincenzo Innocente 52 Proxy Subject Client request( ) Proxy RealSubject request( ) _subject 1 1 _subject : RealSubject request( ) Una richiesta da un client a un server, ... può essere _subject->request(); mediata dal Proxy, che può ... compiere anche altre operazioni (I/O, caching, etc.) Vincenzo Innocente 53 Composite Client Component operation( ) 1..* _children Composite Il client può trattare componenti e compositi usando la stessa interfaccia. La composizione può essere recursiva. Leaf operation( ) operation( ) for c in all _children c->operation(); Vincenzo Innocente Esempio: programmi di grafica vettoriale 54 Composite Shape Client Nel nostro esempio di grafica con Shapes un gruppo può essere considerato un composito e le varie classi concrete sono le leaves. Shape draw( ) 1..* _children Gruppo draw( ) Cerchio, Rettangolo, ... draw( ) for c in all _children c->draw(); Vincenzo Innocente 55 Strategy Il pattern Strategy permette di scegliere l’algoritmo da eseguire a run-time. { ... Nuovi algoritmi possono essere Strategy* s; s->doAlgorithm(); introdotti senza modificare i client. ... } Strategy Client doAlgorithm( ) ConcreteStrategyA ConcreteStrategyBConcreteStrategyC doAlgorithm( ) doAlgorithm( ) Vincenzo Innocente doAlgorithm( ) 56 Observer Observer Subject _observers update( ) _observers : Observer 0..* attach (Observer) notify () for all o in _ observables o->update(); ConcreteObserver _status : Status ConcreteSubject _subject _subject . ConcreteSubject _status : Status status( ) return _status; Lo stato dell’Observer dipende dallo stato del Subject. Il Subject notifica a tutti gli Observer registrati che il suo stato è cambiato. Vincenzo Innocente update( ) _status = _subject->status(); 57 Visitor Client Visitor visit1 (ConcreteElement1) visit2 (ConcreteElement2) Element accept (Visitor) ConcreteVisitor1 visit1 (ConcreteElement1) visit1 (ConcreteElement1) visit2 (ConcreteElement2) visit2 (ConcreteElement2) ConcreteElement1 ConcreteElement2 accept (Visitor v) accept (Visitor v) v->visit1(this) v->visit2(this) ConcreteVisitor2 Permette di aggiungere nuove operazioni a Element senza modificarne l’interfaccia. Per aggiungere nuovi ConcreteElement, bisogna modificare tutti i Visitors. Vincenzo Innocente 58