POO-C
POO-C
POO-C
Dbut du cours (utilisez les flches en haut gauche pour naviguer) Index Tout dans un seul fichier: HTML / PDF
Sommaire
Index Chapitre 0 : Introduction Chapitre 1 : Des objets et des classes Chapitre 2 : Hritage Chapitre 3 : Mmoire Chapitre 4 : Constance Chapitre 5 : Templates et STL Chapitre 6 : Passage par valeur et par rfrence Chapitre 7 : Surcharge des oprateurs et smart pointers Chapitre 8 : Complments sur les types Chapitre 9 : Traitement des erreurs Chapitre 10 : Hritage multiple
Liens et rfrences
Les travaux pratiques associs ce cours Un petit tutoriel: de Java C++ Une intro au toolkit graphique Qt Le site de cplusplus.com et son manuel de rfrence Ainsi que ceux de C++ Reference et STL SGI La documentation automatique avec Doxygen Les extensions Boost La foire Aux Questions: C++ FAQ Le cours C++ de Christian Casteyde Le site de Bjarne Stroustrup l'auteur du C++
Brve historique
Langage C C++
Bjarne Stroustrup, AT&T Bell Labs initialement une extension objet du C (pr-compilateur) plusieurs versions: de 1985 normalisation ANSI / ISO (1998, 2003) C++11 : nouvelle version (aot 2011)
Java
inspir de la partie objet de C++ (et d'ADA, Smalltalk ...)
C#
inspir de Java, de C++, etc.
Python, Ruby
visent la simplicit d'criture et la flexibilit interprts et bass sur le typage dynamique (comme Objective C)
Evolutions rcentes
progression de Objective C, C# et Python aux dpends de Java C/C++ reste stable et (largement) majoritaire
C++ versus C
Avantage : compatibilit C/C++
mme syntaxe de base code C facilement compilable en C++ (faibles diffrences syntaxiques) un programme peut combiner des fichiers C et C++ => idal pour rendre un programme C orient objet
Diffrences
gestion mmoire (pas de garbage collecting, etc.) hritage multiple redfinition des oprateurs templates et STL pas de threads dans le langage (mais bibliothques ad hoc) langage compil (et ... plus rapide !)
En rsum
C++ = langage objet ET procdural
contrairement Java, purement OO vision anachronique: C++ = "sorte de mlange de C et de Java"
Bon cts
orient objet avec l'efficacit du C (et compatible avec C) richesse du langage...
Rfrences et liens
Rfrences
Le langage C++, Bjarne Stroustrup (Pearson) www.cplusplus.com/reference www.cppreference.com
Liens utiles
Travaux Pratiques: www.enst.fr/~elc/cpp/TP.html Introduction au toolkit graphique Qt: www.enst.fr/~elc/qt Boost C++ Libraries: www.boost.org voir aussi liens en 1ere page...
Compilateurs
Attention aux incompabilits entre les versions de C++
syntaxiques binaires le programme et les librairies doivent tre compils avec des versions compatibles de C++
Premier chapitre
Des objets et des classes ...
Programme C++
Un programme C++ est constitut :
de classes rparties dans plusieurs fichiers (comme en Java) (ventuellement) de fonctions et variables globales (comme en C)
Dclarations et dfinitions
Comme en langage C :
dclarations dans fichiers headers : xxx.h (ou .hpp ou .hh) dfinitions dans fichiers d'implmentation : xxx.cpp (ou .cc ou .C)
Rgles de base :
chaque .cpp (dfinitions) correspond un .h (dclarations) le .h dclare l'API publique, c'est une interface avec le monde extrieur
Dclarations et dfinitions
Comme en langage C :
dclarations dans fichiers headers : xxx.h (ou .hpp ou .hh) dfinitions dans fichiers d'implmentation : xxx.cpp (ou .cc ou .C)
Rgles de base :
chaque .cpp (dfinitions) correspond un .h (dclarations) le .h dclare l'API publique, c'est une interface avec le monde extrieur
Remarque
on peut aussi "cacher" des variables ou des fonctions en les dclarant : directement dans le .cpp dans un header priv (exemple: xxx_impl.h) => surtout pour les librairies
Dclaration de classe
// fichier Circle.h : header contenant les dclarations class Circle { public: int x, y; unsigned int radius; virtual void setRadius(unsigned int); virtual unsigned int getRadius() const; virtual unsigned int getArea() const; .... };
Remarques
le ; final est obligatoire aprs la } mme smantique que Java, syntaxe similaire mais ... l'implmentation est (de prfrence) spare des dclarations
Implmentation de classe
// Rappel des dclarations class Circle { public: int x, y; unsigned int radius; virtual void setRadius(unsigned int); virtual unsigned int getRadius() const; virtual unsigned int getArea() const; };
Implmentation
// fichier Circle.cpp : contient l'implementation #include "Circle.h" // ne pas oublier d'inclure le header ! // noter le ::
void Circle::setRadius(unsigned int r) { radius = r; } unsigned int Circle::getRadius() const { return radius; } unsigned int Circle::getArea() const { return 3.1416 * radius * radius; }
Instanciation
// fichier main.cpp : main() est le point d'entre du programme #include "Circle.h" int main() { Circle* c = new Circle(); ..... } // ne pas oublier d'inclure le header
Instanciation
// fichier main.cpp #include "Circle.h" int main() { Circle* c = new Circle(); ..... }
dans les 2 cas: une variable qui pointe sur un objet attention: "rfrence" a un autre sens en C++ ( suivre...)
Gestion mmoire
Java detruit les objets qui n'ont plus de rfrent (garbage collector) C++ ncessite une destruction explicite par l'oprateur delete
Constructeurs
class Circle { int x, y; unsigned int radius; public: Circle(int x, int y, unsigned int r); .... }; Circle::Circle(int _x, int _y, unsigned int _r) { x = _x; y = _y; radius = _r; } Circle* c = new Circle(100, 200, 35);
// declaration
// implementation
// instanciation
Constructeurs (suite)
Deux syntaxes (quasi) quivalentes
Circle::Circle(int _x, int _y, unsigned int _r) { x = _x; y = _y; radius = _r; } Circle::Circle(int _x, int _y, unsigned int _r) : x(_x), y(_y), radius(_r) { } // comme en Java
// propre C++
car C++ cre un constructeur par dfaut mais qui ne fait rien ! => variables pas initialises (contrairement Java)
Conseils
toujours definir au moins un constructeur et toujours initialiser les variables de plus, c'est une bonne ide de dfinir un constructeur sans argument :
class Circle { int x, y; unsigned int radius; public: Circle(int x, int y, unsigned int r); Circle() : x(0), y(0), radius(0) { } .... };
Destruction
Circle* c = new Circle(100, 200, 35); ... delete c; // destruction de l'objet c = NULL; // c pointe sur aucun object }
Remarque
NULL est une macro qui vaut 0 (ce n'est pas un mot-cl)
Destructeur
class Circle { public: virtual ~Circle(); ... };
// destructeur
Circle* c = new Circle(100, 200, 35); ... delete c; // destruction de l'objet c = NULL;
Et en Java ...?
Surcharge (overloading)
Plusieurs mthodes
ayant le mme nom mais des signatures diffrentes pour une mme classe
class Circle { Circle(); Circle(int x, int y, unsigned int r); .... };
Remarques
la valeur de retour ne suffit pas distinguer les signatures applicable aux fonctions "classiques" (hors classes)
Remarques
en nombre quelconque mais toujours en dernier erreur de compilation s'il y a des ambiguits :
class Circle { Circle(); Circle(int x, int y, unsigned int r = 10); Circle(int x = 0, int y = 0, unsigned int r = 10); .... };
// OK // AMBIGU!
Variables de classe
class Circle { public: static const float PI; int x, y; unsigned int radius; ... }; // fichier Circle.h // variable de classe // variables d'instance
Remarques
const (optionnel) indique que la valeur est constante notion similaire aux variables "statiques" du C (d'o le mot-cl)
Exception
les variables de classe const int peuvent tre dfinies dans les headers
// dans Circle.h static const int TAILLE_MAX = 100;
Mthodes de classe
// dclaration: fichier Circle.h class Circle { public: static const float PI; static float getPI() {return PI;} ... }; // appel: fichier main.cpp float x = Circle::getPI();
Namespaces
namespace = espace de nommage
solution ultime aux collisions de noms existent aussi en C#, similaires aux packages de Java
namespace Geom { // dans Circle.h class Circle { ... }; } ---------------------------------------------------------------namespace Math { // dans Math.h class Circle { // une autre classe Circle... ... }; } ---------------------------------------------------------------#include "Circle.h" #include "Math.h" // dans main.cpp
Namespaces
using namespace
modifie les rgles de porte les symboles dclars dans ce namespace deviennent directement accessibles similaire import en Java
namespace Geom { // dans Circle.h class Circle { ... }; } ---------------------------------------------------------------#include "Circle.h" using namespace Geom; // dans main.cpp
int main() { Geom::Circle* c1 = new Geom::Circle(); Circle* c2 = new Circle(); // OK grace a using namespace... ... }
Trois niveaux
private (le dfaut en C++) : accs rserv cette classe protected : idem + sous-classes public NB: Java a un niveau package (dfaut), C++ a galement friend
struct
struct = class + public
struct Truc { ... };
quivaut :
class Truc { public: ... };
struct
est quivalent class en C++ n'existe pas en Java existe en C# mais ce n'est pas une class existe en C mais c'est juste un agrgat de variables
Et pourtant :
unsigned int Circle::getArea() const { return PI * getRadius() * getRadius(); } // idem
unsigned int Circle::getRadius() const { return radius; // comment getRadius() accede a radius ? }
Inline
Indique que la fonction est implmente dans le header
// dans Circle.h class Circle { public: inline int getX() const {return x;} int getY() const {return y;} // pareil: inline est implicite .... }; // inline doit tre prsent si fonction non-membre inline Circle* createCircle() {return new Circle();}
Terminologie
Mthode versus fonction
mthodes d'instance == fonctions membres mthodes de classe == fonctions statiques fonctions classiques == fonctions globales etc.
Doxygen
/** modlise un cercle. * Un cercle n'est pas un carr ni un triangle. */ class Circle { /// retourne la largeur. virtual unsigned int getWidth() const; virtual unsigned int getHeight() const; ///< retourne la hauteur. virtual void setPos(int x, int y); /**< change la position. * voir aussi setX() et setY(). */ ... };
Chapitre 2 : Hritage
Concept essentiel de l'OO
hritage simple (comme Java) hritage multiple ( manier avec prcaution : voir plus loin)
Rgles d'hritage
Constructeurs
jamais hrits
Mthodes
hrites peuvent tre redfinies (overriding) : la nouvelle mthode remplace celle de la superclasse ! ne pas confondre surcharge et redfinition !
Variables
hrites peuvent tre surajoutes (shadowing) : la nouvelle variable cache celle de la superclasse ! viter : source de confusions !
Exemple (dclarations)
// header Rect.h class Rect { int x, y; unsigned int width, height; public: Rect(); Rect(int x, int y, unsigned int width, unsigned int height); virtual void setWidth(unsigned int); virtual void setHeight(unsigned int); virtual unsigned int getWidth() const {return width;} virtual unsigned int getHeight() const {return height;} /*...etc...*/ }; class Square : public Rect { // drivation de classe public: Square(); Square(int x, int y, unsigned int width); virtual void setWidth(unsigned int); virtual void setHeight(unsigned int); }; // redfinition de mthode
Exemple (implmentation)
class Rect { // rappel des dlarations int x, y; unsigned int width, height; public: Rect(); Rect(int x, int y, unsigned int width, unsigned int height); virtual void setWidth(unsigned int); virtual void setHeight(unsigned int); ... }; class Square : public Rect { public: Square(); Square(int x, int y, unsigned int width); virtual void setWidth(unsigned int) virtual void setHeight(unsigned int); };
// implmentation: Rect.cpp void Rect::setWidth(unsigned int w) void Square::setWidth(unsigned int w) {width = w;} {width = height = w;}
Rect::Rect() : x(0), y(0), width(0), height(0) {} Square::Square() {} Square::Square(int x, int y, unsigned int w) : Rect(x, y, w, w) {} /*...etc...*/
Remarques
Drivation de classe
class Square : public Rect { .... };
hritage public des mthodes et variables de la super-classe = extends de Java peut aussi tre private ou protected
1er cas : appel implicite de Rect( ) 2e cas : appel explicite de Rect(x, y, w, w) Pareil en Java, sauf syntaxe : mot-cl super()
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - #include "Shape.h" // dans Circle.h class Circle : public Shape { ... }; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - #include "Shape.h" // dans Rect.h class Rect : public Shape { ... }; //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - #include "Circle.h" #include "Rect.h" // dans main.cpp
A faire systmatiquement en C / C++ pour tous les headers Note: #import fait cela automatiquement mais n'est pas standard
l'option -I du compilateur ajoute un rpertoire de recherche pour < > exemple: -I/usr/X11R6/include
Polymorphisme
3eme caractristique fondamentale de la POO
class Rect { int x, y; unsigned int width, height; public: virtual void setWidth(unsigned int w) {width = w;} ... }; class Square : public Rect { public: virtual void setWidth(unsigned int w) {width = height = w;} ... }; int main() { Rect* obj = new Square(); obj->setWidth(100); } // obj est un Square ou un Rect ? // quelle methode est appele ?
Laison statique
le contraire : la mthode lie au pointeur est appele
Mthodes virtuelles
Deux cas possibles en C++
class Rect { public: virtual void setWidth(unsigned int); }; class Square : public Rect { public: virtual void setWidth(unsigned int); }; int main() { Rect* obj = new Square(); obj->setWidth(100); } // methode virtuelle
Mthodes virtuelles
mot cl virtual => liaison dynamique : Square::setWidth() est appele
Java et C#
mme comportement que mthodes virtuelles
Exceptionnellement
pour optimiser l'excution si on est sr que la fonction ne sera pas redfinie accesseurs, souvent "inline" dans ce cas cas extrmes, mthode appele 10 000 000 fois... en Java on dclarerait la mthode final n'existe pas en C++ => mettre un commentaire
// virtual ncessaire
// virtual implicite
// surcharge de setWidth()
// correct
Mthode abstraite
Spcification d'un concept dont la ralisation peut varier
ne peut pas tre implmente doit tre redfinie (et implmente) dans les sous-classes ad hoc
class Shape { public: virtual void setWidth(unsigned int) = 0; ... };
Classe abstraite
Contient au moins une mthode abstraite
=> ne peut pas tre instancie
Remarque
pas de mot-cl abstract comme en Java il suffit qu'une mthode soit abstraite
Exemple
class Shape { // classe abstraite int x, y; public: Shape() : x(0), y(0) {} Shape(int _x, int _y) : x(_x), y(_y) {} virtual int getX() const {return x;} virtual int getY() const {return y;} virtual unsigned int getWidth() const = 0; virtual unsigned int getHeight() const = 0; virtual unsigned int getArea() const = 0; // ... idem pour setters }; class Circle : public Shape { unsigned int radius; public: Circle() : radius(0) {} Circle(int x, int y, unsigned int r) : Shape(x, y), radius(0) {} virtual unsigned int getRadius() const {return radius;} // redefinition et implementation des methodes abstraites virtual unsigned int getWidth() const {return 2 * radius;} virtual unsigned int getHeight() const {return 2 * radius;} virtual unsigned int getArea() const {return PI * radius * radius;} // ... idem pour setters } // methodes // abstraites
Traitements gnriques
#include #include #include #include #include <iostream> "Shape.h" "Rect.h" "Square.h" "Circle.h"
int main(int argc, char** argv) { Shape** tab = new Shape*[10]; unsigned int count = 0; // tableau de Shape*
tab[count++] = new Circle(0, 0, 100); tab[count++] = new Rect(10, 10, 35, 40); tab[count++] = new Square(0, 0, 60); for (int k = 0; k < count; k++) { cout << "Area = " << tab[k]->getArea() << endl; } }
Note: traitements gnriques != programmation gnrique (que l'on verra plus tard) but (en gros) similaire, approche diffrente
Evolutivit
rajout de nouvelles classes sans modification de l'existant
Interfaces
Classes totalement abstraites
toutes les mthodes sont abstraites aucune implmentation -> pure spcification d'API (Application Programming Interface)
Exemple d'interface
class Shape { // interface // pas de variables d'instance ni de constructeur public: virtual int getX() const = 0; // virtual int getY() const = 0; // virtual unsigned int getWidth() const = 0; // virtual unsigned int getHeight() const = 0; // virtual unsigned int getArea() const = 0; // }; class Circle : public Shape { int x, y; unsigned int radius; public: Circle(); Circle(int x, int y, unsigned int r = 10); // getX() et getY() doivent tre virtual int getX() const {return virtual int getY() const {return virtual unsigned int getRadius() ...etc... } implmentes x;} y;} const {return radius;}
Comment ?
technique de base : hritage -> dcoupage astucieux des mthodes, mthodes intermdiaires ... rappel des mthodes des super-classes :
class NamedRect : public Rect { public: virtual void draw() { // affiche le rectangle et son nom Rect::draw(); // trace le rectangle /* code pour afficher le nom */ } };
// variables d'instance
// variables d'instance
http://www.antlr.org/wiki/display/CS652/Implementing+Polymorphism
Chapitre 3 : Mmoire
Les diffrents types de mmoire
mmoire statique / globale : rserve ds la compilation: variables static ou globales pile / stack : variables locales ("automatiques") et paramtres des fonctions mmoire dynamique / tas / heap : alloue l'excution par new (malloc en C)
void foo() { static int count = 0; count++; int i = 0; i++; int* p = new int(0); (*p)++; }
Mmoire
Dure de vie
mmoire statique / globale : toute la dure du programme pile : pendant l'excution de la fonction mmoire dynamique : de new delete (de malloc free en C)
void foo() { static int count = 0; int i = 0; int* p = new int(0); }
A la sortie de la fonction
count existe encore (et conserve sa valeur) i est dtruite p est dtruite (elle est dans la pile) mais pas ce qu'elle pointe => attention aux fuites mmoire (pas de ramasse miettes en C/C++)
Mmoire : complments
// fichier toto.cpp bool is_valid = true; static const char* errmsg = "Valeur invalide"; void foo() { static int count = 0; int i = 0; int* p = new int(0); is_valid = false; cerr << errmsg << endl; } // globale // statique globale // statique locale // pile // dynamique
les variables globales sont dangereuses !!! il existe un 4e type de mmoire : la mmoire constante/read only (parfois appele statique !)
Java
pas de variables globales ni static (sauf dans les classes) new pas possible sur un type de base
Mmoire et objets
C++ permet d'allouer des objets
dans les trois types de mmoire, contrairement Java !
void foo() { static Square a(5,5,20); Square b(5,5,20); Square* c = new Square(5,5,20); } // statique // pile // dynamique, seul cas en Java
les variables a et b contiennent l'objet impossible en Java : que des types de base ou des rfrences dans la pile la variable c pointe vers l'objet mme chose qu'en Java (sauf qu'il n'y a pas de ramasse miettes en C/C++)
new et delete
chaque new doit correspondre un (et un seul) delete delete p ne fait rien si p vaut NULL (ou 0) ne pas faire delete sur des objets en mmoire statique ou dans la pile ils sont dtruits automatiquement
. versus ->
void foo() { static Square a(5,5,20); Square b(5,5,20); Square* c = new Square(5,5,20); unsigned int w = a.getWidth(); int y = b.getY(); int x = c->getX(); } // statique // pile // dynamique
. pour accder un membre d'un objet (ou d'une struct en C) -> mme chose depuis un pointeur (comme en C) c->getX( ) == (*c).getX( )
Dure de vie
l'objet a est automatiquement cr/dtruit en mme temps que le programme l'objet b est automatiquement cr/dtruit en mme temps que l'instance de Dessin l'objet point par c est typiquement : cr par le constructeur de Dessin detruit par le destructeur de Dessin
Cration de l'objet
class Dessin { static Square a; Square b; Square* c; public: Dessin(int x, int y, unsigned int w) : b(x, y, w), // appelle le constructeur de b c(new Square(x, y, w)) { // cre l'objet point par c } }; // dans un (et un seul) fichier .cpp Square Dessin::a(10, 20, 300); // ne pas repeter "static"
Destruction de l'objet
Il faut un destructeur !
chaque fois qu'un constructeur fait un new (sinon fuites mmoires)
class Dessin { Square b; Square* c; public: Dessin(int x, int y, unsigned int w) : b(x, y, w), c(new Square(x, y, w)) { } virtual ~Dessin() {delete c;} }; // dtruire l'objet cr par le constructeur
Remarques
b pas cr avec new => pas de delete destructeurs gnralement virtuels pour avoir le polymorphisme
Initialisation et affectation
class Dessin { Square b; Square* c; public: Dessin(int x, int y, unsigned int w); virtual ~Dessin() {delete c;} }; void foo() { Dessin d1(0, 0, 50); // d1 contient l'objet Dessin d2(10, 20, 300); d2 = d1; Dessin d3(d1); Dessin d4 = d1; } // affectation (d'un objet existant) // initialisation (d'un nouvel objet) // idem
Initialisation et affectation
void foo() { Dessin d1(0, 0, 50); Dessin d2(10, 20, 300); d2 = d1; // affectation Dessin d3(d1); // initialisation Dessin d4 = d1; // idem }
Problme
le contenu de d1 est copi champ champ dans d2, d3 et d4 => tous les Dessins pointent sur la mme instance de Square ! => elle est dtruite 4 fois quand on sort de foo (et les autres jamais) !
Solution
il faut de la copie profonde, la copie superficielle ne suffit pas problme grral qui n'est pas propre C/C++ : quel que soit le langage chaque dessin devrait avoir son propre Square
dclarer privs l'oprateur d'initialisation (copy constructor) et d'affectation (operator=) implmentation inutile interdit galement la copie pour les sous-classes (sauf si elles redfinissent ces oprateurs)
Dessin::Dessin(const Dessin& from) : Graphique(from) { b = from.b; if (from.c != NULL) c = new Square(*from.c); else c = NULL; } Dessin& Dessin::operator=(const Dessin& from) { Graphique::operator=(from); b = from.b; delete c; if (from.c != NULL) c = new Square(*from.c); else c = NULL; // copie profonde // copie profonde
return *this; }
Complments
Tableaux: new[ ] et delete[ ]
int* tab = new int[100]; delete [] tab; // ne pas oublier les []
Mthodes virtuelles
mthodes virtuelles => destructeur virtuel ne le sont plus dans les constructeurs / destructeurs !
Chapitre 4 : Constance
Dclarations de constantes
1) Macros du prprocesseur
#define PORT 3000 #define HOST "localhost"
substitution textuelle avant la compilation obsolte et dangereux, viter quand c'est possible
2) Enumrations
enum {PORT = 3000}; enum Day {SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY}; // Day = nom de l'enum
pour dfinir des valeurs entires, commencent 0 par dfaut existent aussi en Java (plus sophistiques : classes spciales)
3) Variables constantes
const int PORT = 3000; const char* HOST = "localhost";
ces variables d'instance ne peuvent pas changer elles doivent tre initialises de cette manire
la valeur du pointeur (= sur quelle chane HOST pointe) ? ou la valeur de ce qu'il pointe (= le contenu de la chane "localhost") ?
Par exemple
peut-on ensuite crire ?
HOST = "www.telecom-paristech.fr"; HOST[0] = 'x';
User fred("fred"); char* s = fred.getName(); s[0] = 'x'; // modifie 'name' l'insu de fred !
User fred("fred"); char* s = fred.getName(); s[0] = 'x'; // modifie 'name' l'insu de fred !
Solution
class User { ..... const char* getName() const {return name;} }; User fred("fred"); const char* s = fred.getName(); s[0] = 'x'; // const a droite => const gauche // INTERDIT (erreur de compilation)
en Java : mme problme avec les rfrences, c'est pourquoi les String sont "immuables"
Mthodes "constantes"
const appliqu une mthode
spcifie que la mthode ne modifie pas l'objet permet d'appeler cette mthode si l'objet est constant
class Square { .... public: int getX() const; void setX(int x); .... };
Exemple
const Square s1(0, 0, 100); const Square * s2 = new Square(50, 50, 300); cout << s1.getX() << endl; s2->setX(50); // OK: getX() est const // ERREUR: setX() n'est pas const
// ressource interne
Solution : mutable
Templates (2)
Classes templates
template <class T> class vector { vector() { void add(T elem) { void add(T elem, int pos) { void remove(int pos) { }; template <class T> void sort(vector<T> v) { ..... } vector<int> v; v.add(235); v.add(1); v.add(14); sort(v); ... ... ... ... } } } }
Mais aussi
deque, queue, stack, set, bitset
STL (2)
Algorithmes
manipulent les donnes des conteneurs gnriques
reverse( v.begin(), v.end() );
Itrateurs
sortes de pointeurs gnraliss exemple: v.begin() et v.end()
reverse(v.begin(), v.end());
Documentation
www.cppreference.com ou www.sgi.com/tech/stl
Exemple de vecteur
#include <vector> using namespace std; struct Point { // struct = class + public int x, y; Point() : x(0), y(0) {} Point(int _x, int _y) : x(_x), y(_y) {} }; vector<Point> points; // vecteurs de Points
points.push_back( Point(20, 20) ); points.push_back( Point(50, 50) ); points.push_back( Point(70, 70) ); for (unsigned int i=1; i < points.size(); i++) drawLine(points[i-1].x, points[i-1].y, points[i].x, points[i].y); points.clear(); // vide le vecteur
Exemple de liste
#include <list> using namespace std; list<Point *> plist; // liste de pointeurs
plist.push_back( new Point(20, 20) ); plist.push_back( new Point(50, 50) ); plist.push_back( new Point(70, 70) ); for (list<Point*>::iterator it = plist.begin(); it != plist.end(); ++it) { (*it)->draw(); // ( ) ncessaires car -> est plus prioritaire que * }
Pbm 1 : La liste est recopie inutilement ( suivre...) Pbm 2 : Les objets points ne sont pas dtruits !!!
plist est dans la pile => automatiquement dtruite mais pas les objets crs par new ! 1ere solution :
for (list<Point*>::iterator it = plist.begin(); it != plist.end(); ++it) delete *it;
Remarque : si User n'a pas de sous-classe on peut aussi utiliser : map<string, User> ce qui simplifie la gestion mmoire (pas de new, pas de delete)
la valeur de l'argument est recopie dans le paramtre de la fonction sauf pour les tableaux (l'adresse du 1er lment est recopie) cas par dfaut pour C++ et C# (seule possibilit pour C et Java)
Problmes
1. le contenu de a est recopi inutilement dans s (temps perdu !) 2. recopie pas souhaitable dans certains cas exemple: noeuds d'un graphe pointant les uns sur les autres
1ere tentative
class MySocket { public: MySocket(const char* host, int port); void send(int i); void receive(int& i); void send(string& s); .... }; void MySocket::send(string& s) { // envoie s sur la socket } void foo() { MySocket sock("infres", 6666); string a = "une chaine tres tres tres tres longue....."; sock.send(a); }
Pas satisfaisant
avantage : a n'est plus recopi inutilement dans s inconvnient : send() pourrait modifier a (ce qui n'a pas de sens) amlioration ... ?
Synthse
class MySocket { public: MySocket(const char* host, int port); void send(int i); // par valeur void send(const string& s); // par const rfrence void receive(int& i); // par rfrence };
Passage par valeur argument recopi => pas modifi Passage par const rfrence argument pas recopi, pas modifi alternative au cas prcdent (gros arguments ou qu'il ne faut pas copier) Passage par rfrence argument pas recopi, peut tre modifi cas o on veut rcuprer une valeur
getNameBAD() fait une recopie intermdiaire qui ne sert rien (dans ce cas)
Remarque
Conversions implicites des const rfrences
class User { string name; public: User(string string& n) : name(n) {} }; int main() { User z("Zorgub"); }
// CORRECT
"Zorglub" n'est pas de type string (son type est char *) "Zorglub" est implicitement convertie en string car constructeur :
string::string(const char*);
Rappel
Oprateurs d'initialisation et d'affectation
Dessin(const Dessin& d2); Dessin& operator=(const Dessin& d2); // Dessin d = d2; // d = d2;
Comparaison avec C
Pointeurs: solution quivalente mais plus complique
class MySocket { public: void receive(int& i); void receive(int* pi); }; void MySocket::receive(int& i) { i = ...; } void MySocket::receive(int* pi) { *pi = ...; // noter l'* } void foo() { MySocket sock("infres", 6666); int a; sock.receive(a); sock.receive(&a); } // C++ rfrence (ref en C#) // C++ ou C : pointeur // i est un alias de a
// pi pointe sur a
En Java
PAS de passage par rfrence au sens de C++, C#, Pascal ... PAS de pointeurs au sens de C ou C++
void foo() { MySocket sock = new MySocket("infres", 6666); String buf = new String("mon buffer"); sock.receive(buf); }
Solution
modifier le contenu de l'objet point mais pas le pointeur !
string est une classe "normale" mais les operateurs + et += sont redefinis
class string { friend string operator+(const string&, const char*) string& operator+=(const char*); .... };
operator()
"Objets fonctionnels" : le mme algorithme peut s'appliquer des fonctions ou des objets
operator++
class Integer { Integer& operator++(); Integer operator++(int); }; // prefixe // postfixe
conversions de types
class String { operator char*() const {return c_s;}
};
Avantage
mmoire gre automatiquement : plus de delete !
Exemple
class Shape { // classe de base (Circle drive de Shape) long refcount; public: Shape() : refcount(0) {} void addRef() {++refcount;} void remRef() {if (--refcount == 0) delete this;} // hara kiri 0 ! void setX(int x); ..... }; void foo() { smptr<Shape> p = new Circle(0, 0, 50); p->setX(20);
// smart pointer
vector< smptr<Shape> > vect; // vecteur de smart pointers vect.push_back( new Circle(0, 0, 50) ); vect[0]->setX(20); } // destruction des variables locales p et vect et de ce qu'elles pointent
Ou est la magie ?
Les smart pointers sont des objets qui :
encapsulent un pointeur standard (raw pointer) surchargent le copy constructor et l'operateur = surchargent les oprateurs de drfrencement -> et *
template <class T> class smptr { T* p; public: smptr(T* obj) : p(obj) {if (p != NULL) p->addRef();} ~smptr() {if (p != NULL) p->remRef();} smptr& operator=(T* obj) {....} ..... T& operator*() const {return *p;} // sptr->setX(20) fait // sptr.p->setX(20)
T* operator->() const {return p;} }; void foo() { smptr<Shape> p = new Circle(0, 0, 50); p->setX(20); }
Implmentations et limitations
Il existe plusieurs implmentations
Smart pointers "intrusifs" (intrusive_ptr) imposent d'avoir un compteur dans l'objet Smart pointers "non intrusifs" (shared_ptr) lvent cette restriction mais incompatibles avec pointeurs standard Smart pointers sans comptage de rfrence (scoped_ptr) un seul pointeur par objet Voir smart pointers de Boost et implmentation donne en TP
Attention
ne marchent pas si dpendances circulaires rajouter des verrous s'il y a des threads
Exemple d'implementation
template <class T> class smptr { T* p; public: smptr(T* obj = 0) : p(obj) { if (p != 0) p->addRef(); } smptr(const smptr& ptr) : p(ptr.p) { if (p != 0) p->addRef(); } ~smptr() { if (p != 0) s->remRef(); } smptr& operator=(T* obj) { if (p != 0) p->remRef(); p = obj; if (p != 0) p->addRef(); } smptr& operator=(const smptr& ptr) { if (p != 0) p->remRef(); p = ptr.p; if (p != 0) p->addRef(); } T* operator->() const {return p;} T& operator*() }; const {return *p;}
// destructeur
// ptr = object;
// ptr = ptr2;
Que faire ?
Problmes
"Object" peut ne pas tre modifiable (exple: classe d'une librairie) "Object" finit par contenir tout et n'importe quoi !
Pourquoi ?
Et si on se trompe ?
comment tre sr que obj pointe sur un Button ? => ne JAMAIS utiliser le "cast" du langage C et en Java ? Attraper les exceptions !
Typage dynamique
le type des objets est gnralement dtermin l'excution
@interface Object { // pas de methode draw() } @end @interface Button : Object { } + (void)draw; @end Object* obj = [[Button alloc] init]; [obj draw]; // COMPIL OK : on envoie le message draw a obj // obj "dcide": si obj est un Button draw est execut // classe en Objective-C
similaire au cast du C mais detecte quelques absurdites viter (pas de contrle l'excution)
reinterpret_cast
meme chose en pire
const_cast
pour enlever ou rajouter const au type
RTTI
Accs dynamique au type d'un objet
#include <typeinfo> void printClassName(Shape* p) { cout << typeid(*p).name() << endl; }
RTTI (2)
Ce qu'il ne faut pas faire
void drawShape(Shape *p) { if (typeid(*p) == typeid(Rect) p->Rect::draw(); else if (typeid(*p) == typeid(Square) p->Square::draw(); else if (typeid(*p) == typeid(Circle) p->Circle::draw(); }
Rfrences croises
la methode repaint() depend d'une classe Rect dclare ailleurs ce stade on n'a pas besoin de savoir ce que fait Rect
Cacher l'implmentation
les variables et mthodes internes sont caches dans ButtonImpl ButtonImpl est dclare dans un header priv ButtonImpl.h (pas donn au client de la librairie) Button est une "handle class"
Problme ?
Problme :
erreur de compilation: Rect et ButtonImp sont inconnus !
Solution ?
Rfrences croises
sac de noeuds : les headers vont tous s'inclure les uns les autres !
Cacher l'implmentation
c'est rat : il faut maintenant donner ButtonImpl.h au client !
Cette syntaxe
permet de spcifier qu'qu'une classe existe sans avoir la dclarer n'est valide que pour les pointeurs et les rfrences est valide en langage C pour les pointeurs sur les struct
Exceptions
But : faciliter le traitement des erreurs
permettent de "remonter dans la pile" des appels des fonctions jusqu' un (ou des) endroit(s) bien dfini(s)
Avantages
gestion plus claire, plus centralise, plus homogne des erreurs que les enchanements de fonctions retournant des codes d'erreurs impliquant une gestion des erreurs souvent dficiente car trop complexe
Exceptions : exemple
class MathErr {}; // cf. complment page suivante
class Overflow : public MathErr {}; struct Zerodivide : public MathErr { int x; Zerodivide(int _x) : x(_x) {} }; try { int z = calcul(4, 0) } catch (Zerodivide& e) { cerr << e.x << "divis par 0" << endl; } catch (MathErr) { cerr << "erreur de calcul" << endl; } catch (...) { cerr << "autre erreur" << endl; } int calcul(int x, int y) { return divise(x, y); } int divise(int x, int y) { if (y == 0) throw Zerodivide(x); else return x / y; }
En pratique
en C++ les exceptions sont gnralement des classes, comme en Java : organises en hirarchies (l'hritage multiple est permis) drivant de la classe exception (mais ce n'est pas obligatoire)
#include <exception> class MathErr : std::exception {}; ...etc...
Spcification d'exceptions
Principale diffrence C++ / Java
en Java les mthodes doivent spcifier les exceptions qu'elles peuvent envoyer mais pas en C++ ni en C# (c'tait optionnel en C++, c'est maintenant obsolte)
// Java public int divise(int x, int y) throws Zerodivide, Overflow {...} // C++ int divise(int x, int y); int divise(int x, int y) throw (Zerodivide, Overflow); // OK // OK mais obsolete // NB: throws
Spcification d'exceptions
// Java public int divise(int x, int y) throws Zerodivide, Overflow {...} // C++ int divise(int x, int y);
Avantages et inconvnients
les specifications d'exceptions permettent un meilleur controle mais elle rduisent la puissance de l'hritage : une mthode rdfinie dans une sous-classe ne peut pas spcifier de nouvelles exceptions ce qui amne des complications ou des acrobaties... => choix diffrents selon les langages
Handlers
std::set_terminate() et std::set_unexpected() dans <exception>
Redclenchement
try { ..etc.. } catch (MathErr& e) { if (can_handle(e)) { ..etc.. return; } else { ..etc.. throw; // relance l'exception } }
Constructeurs
class Rect { int x, y, w, h; public: Rect(int x, int y, int width, int height); ... }; class Name { std::string name; public: Name(const std::string&); ... }; class NamedRect : public Rect, public Name { public: NamedRect(const std::string& s, int x, int y, int w, int h) : Rect(x,y,w,h), Name(s) {} };
Ambiguits
class Rect { int x, y, w, h; public: virtual void draw(); }; class Name { int x, y; public: virtual void draw(); }; class NamedRect : public Rect, public Name { public: virtual void draw() { Rect::draw(); Name::draw(); } }
redfinition de NamedRect::draw() pas obligatoire mais prfrable mme principe pour variables
"using"
class A { public: int foo(int); char foo(char); }; class B { public: double foo(double); }; class AB : public A, public B { public: using A::foo; using B::foo; char foo(char); // redefinit A::foo(char) }; AB ab; ab.foo(1); ab.foo('a'); ab.foo(2.);
Duplication de bases
class Shape { int x, y; }; class Rect : public Shape { // ... }; class Name : public Shape { // ... }; class NamedRect : public Rect, public Name { // ... };
la classe Shape est duplique dans NameRect mme principe pour accder aux mthodes et variables
float m = (Rect::x + Name::x) / 2.;
Bases virtuelles
class Shape { int x, y; }; class Rect : public virtual Shape { // ... }; class Name : public virtual Shape { // ... }; class NamedRect : public Rect, public Name { // ... };
la classe Shape n'est PAS duplique dans NameRect attention: surcharge en traitement et espace mmoire utilisation systmatique dcourage
Remarque
pas d'accs aux champs de la classe imbriquante (!= Java)
Plus d'infos
toutes les rponses aux questions possibles et impossibles : C++ FAQ LITE le site de Boost C++ un site intressant sur les smart pointers un site traitant des garbage collectors en C++