Vincenzo Innocente1 Introduction to Object-Oriented Programming in C++ Vincenzo Innocente CERN, Geneva, Switzerland
Vincenzo Innocente2 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 Innocente3 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 Innocente4 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 Innocente5 Concetti base dell’OO Classi ed oggetti Incapsulamento Relazione di ereditarietà Polimorfismo Programmazione Generica ( C++ )
Vincenzo Innocente x7b03a928 Puntatori Riferimento ad una locazione di memoria j 12 ptr int main() { int j = 12; return 0; } int *ptr = &j; #include cout << *ptr << endl; j = 24; cout << *ptr << endl; cout << ptr << endl; indirizzo di memoria 24
Vincenzo Innocente7 Puntatori Puntatore nullo #include int main() { int j = 12; int *ptr = 0; cout << *ptr << endl; // crash ! return 0; } Segmentation violation (core dumped) j 12 ptr
Vincenzo Innocente8 Puntatori: allocazione dinamica Riferimento ad una locazione di memoria #include int main() { int *ptr = new int; *ptr = 12; cout << *ptr << endl; delete ptr; return 0; } 12 ptr 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 Innocente9 Classi e Oggetti Un esempio di programma “orientato ad oggetti”: un videogioco
Vincenzo Innocente10 SoldatoSoldatoClienteCliente
Vincenzo Innocente11 Class Vector An example: un tri-dimentional vector With respect to a structure, a class contains functions beside data 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; }; Vector.h #include “Vector.h” Vector::Vector(double x, double y, double z) : _x(x), _y(y), _z(z) {} 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); } Vector.cc data functions or methods constructor copy constructor
Vincenzo Innocente12 Class Vector #include #include “Vector.h” int main() { Vector v(1, 1, 0); cout << “ v = (“ << v.x() << “,” << v.y() << “,” << v.z() << “)” << endl; cout << “ r = “ << v.r(); cout << “ theta = “ << v.theta() << endl; } main.cc How to use Vector : v = (1, 1, 0) r = theta = Output: invoke the constructor
Vincenzo Innocente13 Class Vector #include #include “Vector.h” int main() { Vector v(1, 1, 0); cout << “ V = (“ << v._x << “,” // << v._y << “,” // does not << v._z << “)” << endl; // compile ! cout << “ r = “ << v.r(); cout << “ theta = “ << v.theta() << endl; } main.cc Data access protection:
Vincenzo Innocente14 #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); } Interface and implementation Protected data are not accessible outside the class 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; }; Vector.hh Vector.cc const
Vincenzo Innocente15 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 Innocente16 Operators It is possible to redefine +, -, *, [], ++, ==,... 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.hh 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); } Vector.cc
Vincenzo Innocente17 Operators Example: #include #include “Vector.h” int main() { Vector v1(1, 0, 0), v2(0, 1, 0); Vector v; v = v1 + v2; cout << “ v = “ << v << endl; cout << “ r = “ << v.r(); cout << “ theta = “ << v.theta() << endl; } main.cc v = (1, 1, 0) r = theta = Output: redefinition of <<
Vincenzo Innocente18 Operators Example: #include #include “Vector.h” #include “Matrix.h” // matrix 3x3 int main() { Vector v1(1, 1, 0); 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; } main.cc
Vincenzo Innocente19 Operator overloading It is possible to define functions with the same name but different arguments class Vector { public:... Vector operator*(double s) const; double operator*(const Vector& v) const; protected: double _x, _y, _z; }; Vector.hh 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); } Vector.cc
Vincenzo Innocente20 Static Functions and data Some functions can be common to the whole class 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.h #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; } Particle.cc
Vincenzo Innocente21 Classi astratte Esempio classico: Shape Tutti oggetti nella finestra hanno comportamenti comuni che possono essere considerati in astratto:Tutti oggetti nella finestra hanno comportamenti comuni che possono essere considerati in astratto: –disegna, sposta, ingrandisci, etc.
Vincenzo Innocente22 Ereditarietà e riuso del codice Class CenteredShape: public Shape { public: CenteredShape( Point2d c ) : center_( c ) { /*draw();*/ } ~Circle() { /*cancel();*/ } void moveAt( const Point2d& ); void moveBy( const Vector2d& ); virtual void scale( double ) = 0; virtual void rotate( double ) = 0; virtual void draw() const = 0; virtual void cancel() const = 0; protected: Point2d center_; }; CenteredShape.h Non si possono chiamare metodi virtuali in costruttori e distruttori (troppo presto, troppo tardi) #include “CenteredShape.hh” class Square : public CenteredShape { public: Square( Point2d lowerCorner, Point2d upperCorner ) : 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_; }; Square.h
Vincenzo Innocente23 Quadrato 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_; }; Square.h #include “Square.h” 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); } void Square::rotate( double phi ) { cancel(); centerToUpperCorner_.rotate( phi ); draw(); } 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(); } Square.cc upperCorner loweCorner centerToUpperCorner _
Vincenzo Innocente24 Circle.h Cerchio Costruttore Distrutt ore Nome della classe Punto e virgola! Point2d : classe che rappresenta un punto in 2 dimensioni. public: Circle(Point2d center, double radius); ~Circle(); void moveAt(const Point2d & p); void moveBy(const Point2d & p); void scale(double s); void rotate(double phi); void draw() const; void cancel() const; “Dati” privati (Attributi, membri) class Circle { }; private: Point2d center_; double radius_; Interfaccia Pubblica Metodi: operazioni sugli oggetti # include “Circle.h” void Circle::draw() const { const int numberOfPoints = 100; float x[numberOfPoints], y[numberOfPoints]; float phi = 0, deltaPhi = 2*M_PI/100; for ( int i = 0; i < numberOfPoints; ++i ) { x[i] = center_.x() + radius_ * cos( phi ); y[i] = center_.y() + radius_ * sin( phi ); phi += dphi; } polyline_draw(x, y, numberOfPoints ); } void Circle::moveAt( const Point2d& p ) { cancel(); center_ = p; draw(); } void Circle::scale( double s ) { cancel(); radius_ *= s; draw(); } Circle::Circle( Point2d c, double r ) : center_( c ), radius_( r ) { draw(); } Circle::~Circle() { cancel(); } Circle.cc #include “Circle.h” int main() { Circle c( Point2d(10, 10), 5 ); c.draw(); c.moveAt(Point2d(20, 30)); return 0; } Main.cc
Vincenzo Innocente25 Codice Applicativo (Client) #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. ); } for ( int i = 0; i < 10; ++i ) circles[ i ]->draw(); return 0; } Main.cc Come gestire cerchi e quadrati insieme? Costruisce un vettore di puntatori a cerchi, crea oggetti in memoria e salva i loro puntatori nel vettore. Itera sul vettore e invoca draw() per ogni elemento
Vincenzo Innocente26 Programmazione generica Funzioni Generiche template inline T min(const T& a, const T& b) { return a < b ? a:b; } class P { public: P(int ix,int iy):x(ix),y(iy){} bool operator<(const P& b) const { return y<b.y ||( y==b.y && x<b.x ); } private: float x; float y; }; P.h int main() { float a = min(3.,2.); int i = min(2,3); P p1(1.,2.), p2(3.,1.); P p3 = min(p1,p2); return 0; } Main.cc Usa min Usa min “<” deve essere definito per “T”
Vincenzo Innocente27 Programmazione generica Classi Generiche template class Vector3d { public: Vector3d(const T*a) { copy(a,a+3,&x[0]); } Vector3d & operator+=(const Vector3d & b) {x[0]+=b.x[0]; x[1]+=b.x[1]; x[2]+=b.x[2]; return *this;} private: T x[3]; }; Vector3d.h #include “vector3d.h” int main() { Vector3d a, b,c; c+=b+=a; Vector3d c1, c2; c2+=c1; return 0; } Main.cc Ritorna se stesso per permettere la concatenazione di operatori “copy” è una funzione generica di STL
Vincenzo Innocente28 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 Innocente29 Un semplice vettore #include int main() { vector v; // creamo un vettore di interi vuoto cout << v.size() << endl; // stampa dimensione di v: zero for ( int i = 0; i != 10; ++i ) // un loop da 0 a 9 v.push_back( i ); // aggiungiamo un intero in coda a v 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 ::const_iterator p; for ( p = v.begin(); p != v.end(); ++p ) // “itera dall’inizio alla fine di v” cout << (*p) << “ “; cout << endl; return 0; } Main.cc
Vincenzo Innocente30 Un semplice vettore begin() end() end() p p pp p p
Vincenzo Innocente31 Vettore di classe astratta Shape* #include “Circle.h” #include “Square.h” #include int main() { vector 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 ::const_iterator p; for ( p = vs.begin(); p != vs.end(); ++p ) // loop sull’intero vettore (*p)->draw(); // disegniamo ogni Shape return 0; } Main.cc
Vincenzo Innocente32 class Shape { public: Shape() { } virtual ~Shape() { } virtual void moveAt( const Point2d& ) = 0; virtual void scale( double s ) = 0; virtual void rotate( double phi ) = 0; virtual void draw() const = 0; virtual void cancel() const = 0; }; Shape.h Interfaccia astratta Interfaccia di metodi puramente virtuali #include “Shape.h” class Square : public Shape { // …. Il resto tutto uguale a prima }; Square.h #include “Circle.h” #include “Square.h” int main() { 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; } for ( int i = 0; i < 10; i++ ) shapes[ i ]->draw(); return 0; } Main.cc
Vincenzo Innocente33 Superfici e traiettorie Nel tracking spesso è necessario calcolare intersezioni tra curve (tracce) e superfici (elementi di detector)
Vincenzo Innocente34 Superfici e traiettorie Interfaccia delle diverse Trajectory #include “Trajectory.h” class Line : public Trajectory { public: virtual Point position(double s); virtual Vector direction(double s); public: Point origin_; Vector direction_; }; Line.h #include “Trajectory.h” class Helix : public Trajectory { public: virtual Point position(double s); virtual Vector direction(double s); }; Helix.h class Trajectory { public: virtual Point position(double s) = 0; virtual Vector direction(double s) = 0; }; Trajectory.h
Vincenzo Innocente35 Superfici e traiettorie Implementazione #include “Trajectory.h” // … vuoto... Trajectory.cc #include “Line.h” Line::Line(const Point& o, constVector& d) : origin_( o ), direction_( d.unit() ) { } Point Line::position(double s) { return ( origin_ + s * direction_ ); } Line.cc #include “Helix.h” Helix::Helix() { } Point Helix::position(double s) { // implementazione } Helix.cc
Vincenzo Innocente36 #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_; }; Plane.h class Surface { public: virtual distance(const Point& p) = 0; virtual derDist(const Point& p, const Vector& r) = 0; }; Surface.h Superfici e traiettorie Interfaccia delle varie Surface #include “Surface.h” class Cylinder : public Surface { public: virtual distance(const Point& p); virtual derDist(const Point& p, const Vector& r); }; Cylinder.h distanza (con segno) di un punto dalla superficie
Vincenzo Innocente37 Superfici e traiettorie Surface è una classe astratta #include “Plane.h” Plane::distance(const Point& p) { return ( _dist - ( (p - origin_) * direction_) ); } Plane::derDist(const Point& p, const Vector& r) { return - r * _direction; } Plane.cc #include “Cylinder.h” Cylinder::distance(const Point& p) { /*... */ } Cylinder::derDist(const Point& p, const Vector& r) { /*... */ } Cylinder.cc #include “Surface.h” // vuoto Surface.cc
Vincenzo Innocente38 Superfici e traiettorie Interfaccia di Intersection 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_; }; Intersection.h forward class declaration
Vincenzo Innocente39 Superfici e traiettorie Implementazion e dell’algoritmo #include “Intersection.h” #include #include “Surface.h” #include “Trajectory.h” const int maxIterations 20 const double sMax 1.e+6 const double accuracy1.e-3 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; Intersection.cc // controlla che test è tra s1 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 Innocente40 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 I ntersection continua a funzionare senza modificare una linea di codice! E’ possibile rendere anche Intersection astratto...
Vincenzo Innocente41 “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 Innocente42 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 Innocente43 Unified Modeling Language Class Diagrams Sequence and Collaboration Diagrams Use Case Diagrams State Diagrams
Vincenzo Innocente44 UML Model of “Shape”
Vincenzo Innocente45 Class Diagram
Vincenzo Innocente46 Class Diagram
Vincenzo Innocente47 Object Sequence Diagram
Vincenzo Innocente48 Object Collaboration Diagram
Vincenzo Innocente49 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 E. Gamma et al., Design Patterns
Vincenzo Innocente50 Factory AbstractProduct ConcreteProduct1 ConcreteProduct2 Factory createProduct1 () : AbstractProduct createProduct2 () : AbstractProduct Client I client possono richiedere la creazione di un prodotto senza dipendervi. La Factory dipende dai prodotti concreti, mentre i client dipendono solo AbstractPro duct.
Vincenzo Innocente51 Singleton 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. Singleton _instance : Singleton instance () : Singleton specificService () if (_instance==0) _instance = new Singleton(); return _instance; user_code() { Singleton::instance()- >specificService(...); } user_code() { Singleton::instance()- >specificService(...); }
Vincenzo Innocente52 Template Singleton Un Template Singleton può essere specializzato usando la classe stessa come argomento del template. Se V ha un constructor privato, Singleton deve essere friend di V (non tutti i compilatori lo supportano…). if (_instance==0) _instance = new T(); return _instance; Singleton _instance : T instance () : T V V( ) specificService( ) Singleton{V} T class V : public Singleton { public: specificService(...); private: V(); friend class Singleton ; }; user_code() { V::instance() ->specificService(...); } class V : public Singleton { public: specificService(...); private: V(); friend class Singleton ; }; user_code() { V::instance() ->specificService(...); }
Vincenzo Innocente53 Proxy Una richiesta da un client a un server, può essere mediata dal Proxy, che può compiere anche altre operazioni (I/O, caching, etc.) Subject request( ) RealSubject request( ) Proxy _subject : RealSubject request( ) 1 1 Client _subject... _subject->request();...
Vincenzo Innocente54 Composite Leaf operation( ) Component operation( ) Composite operation( ) 1..* Client for c in all _children c->operation(); _children Il client può trattare componenti e compositi usando la stessa interfaccia. La composizione può essere recursiva. Esempio: programmi di grafica vettoriale
Vincenzo Innocente55 Composite Shape Cerchio, Rettangolo,... draw( ) Shape draw( ) Gruppo draw( ) 1..* Client for c in all _children c->draw(); _children Nel nostro esempio di grafica con Shapes un gruppo può essere considerato un composito e le varie classi concrete sono le leaves.
Vincenzo Innocente56 Strategy Il pattern Strategy permette di scegliere l’algoritmo da eseguire a run-time. Nuovi algoritmi possono essere introdotti senza modificare i client. ConcreteStrategyA doAlgorithm( ) ConcreteStrategyB doAlgorithm( ) ConcreteStrategyC doAlgorithm( ) Client Strategy doAlgorithm( ) {... Strategy* s; s->doAlgorithm();... }
Vincenzo Innocente57 Observer Lo stato dell’ Observer dipende dallo stato del Subject. Il Subject notifica a tutti gli Observer registrati che il suo stato è cambiato. Observer update( ) Subject _observers : Observer attach (Observer) notify () 0..* for all o in _ observables o->update(); _observers return _status; _status = _subject->status(); ConcreteSubject _status : Status status( ) ConcreteObserver _status : Status _subject. ConcreteSubject update( ) _subject
Vincenzo Innocente58 Visitor visit1 (ConcreteElement1) visit2 (ConcreteElement2) ConcreteVisitor1 visit1 (ConcreteElement1) visit2 (ConcreteElement2) ConcreteVisitor2 visit1 (ConcreteElement1) visit2 (ConcreteElement2) Element accept (Visitor) ConcreteElement1 accept (Visitor v) ConcreteElement2 accept (Visitor v) Client v->visit1(this) v->visit2(this) Permette di aggiungere nuove operazioni a Element senza modificarne l’interfaccia. Per aggiungere nuovi ConcreteElement, bisogna modificare tutti i Visitor s.