l'API (méthodes pour communiquer avec les autres objets) est public
l'implémentation (variables et méthodes internes) est private ou protected
Encapsulation / droits d'accès (3)
class Circle {
friend class Manager;
friend bool equals(const Circle*, const Circle*);
...
};
friend donne accès à tous les champs de Circle
à une autre classe : Manager
à une fonction : bool equals(const Circle*, const Circle*)
struct
struct = class + public
struct Truc {
...
};
équivaut à :
class Truc {
public:
...
};
en Java struct n'existe pas, en C# ce n'est pas une classe
Méthodes d'instance: où est la magie ?
Toujours appliquées à un objet
class Circle {
unsigned int radius;
int x, y;
public:
virtual unsigned int getRadius() const;
virtual unsigned int getArea() const;
};
int main() {
Circle* c = new Circle(100, 200, 35);
unsigned int r = c->getRadius(); // OK
unsigned int a = getArea(); // INCORRECT: POURQUOI?
}
Et pourtant :
unsigned int Circle::getArea() const {
return PI * getRadius() * getRadius(); // idem
}
unsigned int Circle::getRadius() const {
return radius; // comment getRadius() accede a radius ?
}
Le this des méthodes d'instance
Paramètre caché this
pointe sur l'objet qui appelle la méthode
permet d'accéder aux variables d'instance
unsigned int Circle::getArea() const {
return PI * radius * getRadius();
}
Circle* c = ...;
unsigned int a = c->getArea();
Transformé par le compilateur en l'équivalent de
unsigned int Circle::getArea(Circle* this) const {
return Circle::PI * this->radius * this->getRadius();
}
Circle* c = ...;
unsigned int a = Circle::getArea(c);
Inlines
Méthodes implémentées dans les headers
class Circle {
public:
// inline implicite pour les methodes
unsigned int getRadius() const {return radius;}
....
};
inline Circle* createCircle() {return new Circle();}
A utiliser avec discernement
+ : rapidité (theoriquement pas d'appel fonctionnel)
+/- : lisibilité
- : augmente taille du binaire généré
- : contraire au principe d'encapsulation
Point d'entrée du programme
int main(int argc, char** argv)
même syntaxe qu'en C
arc : nombre d'arguments
argv : valeur des arguments
argv[0] : nom du programme
valeur de retour : normalement 0, indique une erreur sinon
Terminologie
Méthode versus fonction
méthodes d'instance == fonctions membres
méthodes de classe == fonctions statiques
fonctions classiques == fonctions globales
etc.
Termes interchangeables selon auteurs
Chapitre 2 : Héritage
Concept essentiel de l'OO
héritage simple (comme Java)
héritage multiple (à manier avec précaution !)
Règles d'héritage
Constructeurs
jamais hérités
Méthodes
héritées
peuvent être redéfinies (overriding) :
la nouvelle méthode remplace celle de la superclasse
! ne pas confondre surcharge et redéfinition !
Variables
héritées
peuvent être surajoutées (shadowing) :
la nouvelle variable cache celle de la superclasse
! à éviter : source de confusions !
Exemple (déclarations)
// 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 { // héritage des variables et méthodes
public:
Square();
Square(int x, int y, unsigned int width);
virtual void setWidth(unsigned int); // redéfinition de 2 méthodes
virtual void setHeight(unsigned int);
};
Exemple (implémentation)
class Rect { // rappel des délarations
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);
};
// implémentation: Rect.cpp
void Rect::setWidth(unsigned int w) {width = w;}
void Square::setWidth(unsigned int 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
Héritage de classe
class Square : public Rect {
....
};
héritage public des méthodes et variables de la super-classe
= extends de Java
peut aussi être private ou protected
Chaînage des constructeurs
Square::Square() {}
Square::Square(int x, int y, unsigned int w) : Rect(x, y, w, w) { }
1er cas : implicite
2e cas : explicite == super() de Java
Headers et inclusions multiples
Problème
class Shape { // dans Shape.h
...
};
#include "Shape.h" // dans Circle.h
class Circle : public Shape {
...
};
#include "Shape.h" // dans Rect.h
class Rect : public Shape {
...
};
#include "Circle.h" // dans main.cpp
#include "Rect.h"
int main() {
...
};
Transitivité des inclusions
le header Shape.h est inclus 2 fois dans main.cpp
=> la classe Shape est déclarée 2 fois => erreur de syntaxe !
Headers et inclusions multiples (2)
Empêcher les redéfinitions
#ifndef _Shape_h_ // dans Shape.h
#define _Shape_h_
class Shape {
...
};
#endif
A faire systématiquement en C et en C++ pour tous les headers
Remarques
#import : un #include intelligent (mais pas standard)
les " " ou < > précisent l'espace de recherche
(programme vs. librairies)
#include <iostream>
#include "Circle.h"
l'option -I du compilateur ajoute un répertoire de
recherche pour < >
exemple: -I/usr/X11R6/include
Polymorphisme
3eme caractéristique 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 est un Square ou un Rect ?
obj->setWidth(100); // quelle methode est appelée ?
}
Polymorphisme et liaison dynamique
Polymorphisme
un objet peut être vu sous plusieurs formes
Rect* obj = new Square(); // obj est un Square ou un Rect ?
obj->setWidth(100); // quelle methode est appelée ?
Liaison dynamique (ou "tardive")
la méthode liée à l'instance est appelée
le choix de la méthode se fait à l'exécution
mécanisme essentiel de l'OO !
Laison statique
le contraire : la méthode liée au pointeur est appelée
Méthodes virtuelles
Deux cas possibles en C++
class Rect {
public:
virtual void setWidth(unsigned int); // methode virtuelle
};
class Square : public Rect {
public:
virtual void setWidth(unsigned int);
};
int main() {
Rect* obj = new Square();
obj->setWidth(100);
}
Méthodes virtuelles
mot clé virtual
=> liaison dynamique : Square::setWidth() est appelée
Méthodes non virtuelles
PAS de mot clé virtual
=> liaison statique : Rect::setWidth() est appelée
Méthodes virtuelles: redéfinition
class Rect {
public:
virtual void setWidth(unsigned int); // virtual nécessaire
};
class Square : public Rect {
public:
/*virtual*/ void setWidth(unsigned int); // virtual implicite
};
Les redéfinitions des méthodes virtuelles sont virtuelles
même si virtual est omis => virtual important dans les classes de base !
Elles doivent avoir la même signature
sinon c'est une autre fonction (surcharge) !
sauf que le type de retour peut être une sous-classe (covariance des types)
Méthodes virtuelles: surcharge
Il faut rédéfinir toutes les variantes
si on redéfinit l'une d'entres-elles
class Rect {
public:
virtual void setWidth(unsigned int);
virtual void setWidth(unsigned double); // surcharge
};
pas correct :
class Square : public Rect {
public:
virtual void setWidth(unsigned int);
};
correct :
class Square : public Rect {
public:
virtual void setWidth(unsigned int);
virtual void setWidth(unsigned double);
};
Pourquoi des méthodes virtuelles ?
Programmation orientée objet
les méthodes d'instance doivent généralement être virtuelles
pour éviter les incohérences : exemple :
Rect* obj = new Square();
obj->setWidth(100);
setWidth() pas virtuelle => Square pas carré !
Java et C#
méthodes virtuelles par défaut
Pourquoi des méthodes NON virtuelles ?
A EVITER !
principale raison: compatibilité avec le C
Cas particuliers
optimiser l'exécution
accesseurs (getters, setters), et encore !
cas extrêmes (méthode appelée 10 000 000 fois...)
redéfinir des méthodes avec des signatures différentes
Méthode abstraite
Spécification d'un concept dont la réalisation peut varier
ne peut pas être implémentée
doit être redéfinie (et implémentée) dans les sous-classes ad hoc
class Shape {
public:
virtual void setWidth(unsigned int) = 0;
...
};
en C++ : virtual et = 0 (pure virtual function)
en Java et en C# : abstract
Classe abstraite
Contient au moins une méthode abstraite
=> ne peut pas être instanciée
Les classes héritées instanciables :
doivent implémenter toutes les méthodes abstraites
class Shape { // classe abstraite
public:
virtual void setWidth(unsigned int) = 0;
...
};
class Rect : public Shape { // Rect peut être instanciée
public:
virtual void setWidth(unsigned int);
...
};
Classes abstraites (2)
Objectifs
"commonaliser" les déclarations de méthodes (généralisation)
-> permettre des traitements génériques sur une hiérarchie de classes
imposer une spécification
-> que les sous-classes doivent obligatoirement implémenter
principe d'encapsulation
-> séparer la spécification et l'implémentation
Remarque
pas de mot-clé "abstract" comme en Java
il suffit qu'une méthode 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; // methodes
virtual unsigned int getHeight() const = 0; // abstraites
virtual unsigned int getArea() const = 0;
};
class Circle : public Shape {
unsigned int radius;
public:
Circle();
Circle(int x, int y, unsigned int r);
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;}
}
Traitements génériques
#include <iostream>
#include "Shape.h"
#include "Rect.h"
#include "Square.h"
#include "Circle.h"
int main(int argc, char** argv) {
Shape** tab = new Shape*[10]; // tableau de Shape*
unsigned int count = 0;
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;
}
}
Bénéfices du polymorphisme (1)
Gestion unifiée
des classes dérivant de la classe abstraite
sans avoir besoin de connaître leur type !
contrairement à la programmation "classique" (en C, etc.)
#include <iostream>
#include "Shape.h"
void printAreas(Shape** tab, int count) {
for (int k = 0; k < count; k++) {
cout << "Area = " << tab[k]->getArea() << endl;
}
}
Evolutivité
rajout de nouvelles classes sans modification de l'existant
Bénéfices du polymorphisme (2)
Spécification indépendante de l'implémentation
les classes se conforment à une spécification commune
=> indépendance des implémentations des divers "modules"
=> développement en parallèle par plusieurs équipes
Interfaces
Classes totalement abstraites
toutes les méthodes sont abstraites
aucune implémentation
-> pure spécification d'API (Application Programming Interface)
En C++: cas particulier de classe abstraite
pas de mot-clé interface comme en Java
pas indispensable car C++ supporte l'héritage multiple
Exemple d'interface
class Shape { // interface
// pas de variables d'instance ni de constructeur
public:
virtual int getX() const = 0; // abstract
virtual int getY() const = 0; // abstract
virtual unsigned int getWidth() const = 0; // abstract
virtual unsigned int getHeight() const = 0; // abstract
virtual unsigned int getArea() const = 0; // abstract
};
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 implémentées
virtual int getX() const {return x;}
virtual int getY() const {return y;}
virtual unsigned int getRadius() const {return radius;}
...etc...
}
Complément: factorisation du code
Eviter les duplications de code
gain de temps
évite des incohérences
lisibilité par autrui
maintenance : facilite les évolutions ultérieures
Comment ?
technique de base : héritage
-> découpage astucieux des méthodes, méthodes intermédiaires ...
rappel des méthodes 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 */
}
};
Classes imbriquées (1)
class Rect {
class Point { // classe imbriquee
int x, y;
public:
Point(x, y);
};
Point p1, p2; // variables d'instance
public:
Rect(int x1, int y1, int x2, int y2);
};
Rect::Rect(int x1, int y1, int x2, int y2)
: p1(x1,y1), p2(x2,y2) { } // appel du const. de la classe imbriquée
Rect::Point::Point(int _x, int _y)
: x(_x), y(_y) { }
Technique de composition très utile
souvent préférable à l'héritage multiple (à suivre...)
Classes imbriquées (2)
class Rect {
class Point { // classe imbriquee
int x, y;
public:
Point(x, y);
};
Point p1, p2; // variables d'instance
public:
Rect(int x1, int y1, int x2, int y2);
};
Visibilité des champs depuis la classe imbriquée
les champs de Rect sont automatiquement visibles depuis Point en Java
mais pas en C++ !
Méthodes virtuelles: comment ça marche ?
Tableau de pointeurs de fonctions (vtable)
1 vtable par classe
chaque objet pointe vers la vtable de sa classe
=> coût un peu plus élévé (double indirection)
Chapitre 3 : Mémoire
Les différents types de mémoire
mémoire statique (ou globale) :
réservée dès la compilation, variables static
pile (stack) :
variables locales ("automatiques") des fonctions
mémoire dynamique (tas/heap) : allouée à l'exécution par new
(malloc en C)
void foo() {
static int count = 0; // statique
count++;
int i = 0; // pile
i++;
int* p = new int(0); // dynamique
(*p)++; // NB: ne pas oublier les parenthèses!
}
que valent count, i, *p si on appelle foo() deux fois ?
Remarque: il existe un 4e type : la mémoire constante "read only"
Mémoire
Durée de vie
mémoire statique :
toute la durée du programme
pile :
pendant l'exécution de la fonction
mémoire dynamique :
de new à delete (de malloc à free en langage C)
void foo() {
static int count = 0; // statique
int i = 0; // pile
int* p = new int(0); // dynamique
}
A la sortie de la fonction
count existe encore (et conserve sa valeur)
i est détruite
p est détruite (elle est dans la pile) mais pas
ce qu'elle pointe !
=> attention aux fuites mémoire !
Mémoire et objets
C++ permet d'allouer des objets
dans les trois types de mémoire, contrairement à Java !
void foo() {
static Square a(5,5,20); // statique
Square b(5,5,20); // pile
Square* c = new Square(5,5,20); // dynamique
}
les variables a et b contiennent l'objet
impossible en Java: que des types de base ou des références dans la pile
la variable c pointe vers l'objet
même chose qu'en Java (sauf qu'il n'y a pas de ramasse miettes en C++)
Création et destruction des objets
void foo() {
static Square a(5,5,20); // statique
Square b(5,5,20); // pile
Square* c = new Square(5,5,20); // dynamique
}
Dans tous les cas
Constructeur appelé quand l'objet est créé
ainsi que ceux des superclasses (chaînage descendant des constructeurs)
Destructeur appelé quand l'objet est détruit
ainsi que ceux des superclasses (chaînage ascendant des destructeurs)
Création et destruction des objets (2)
void foo() {
static Square a(5,5,20); // statique
Square b(5,5,20); // pile
Square* c = new Square(5,5,20); // dynamique
}
Utilisation de new et delete
à chaque new doit correspondre un (et un seul) delete
jamais de delete sur des objets statiques ou dans la pile
(détruits automatiquement)
Comment se passer de delete ?
avec des smart pointers (à suivre)
la mémoire est toujours récupérée en fin de programme
aucun delete = solution acceptable si peu d'objets
. versus ->
void foo() {
static Square a(5,5,20); // statique
Square b(5,5,20); // pile
Square* c = new Square(5,5,20); // dynamique
unsigned int w = a.getWidth();
int y = b.getY();
int x = c->getX();
}
. pour accéder à un membre d'un objet (ou d'une struct en C)
-> même chose depuis un pointeur (comme en C)
c->getX() équivaut à (*c).getX()
Objets contenant des objets
class Dessin {
static Square a; // var. de classe qui contient l'objet
Square b; // var. d'instance qui contient l'objet
Square* c; // var. d'instance qui pointe vers un objet
static Square* d; // var. de classe qui pointe vers un objet
};
Durée de vie : même principe
static (cas a et d) : même durée de vie que le programme
sinon (cas b et c) : même durée de vie que l'objet contenant
NB : l'objet pointé (cas c et d) est en mémoire dynamique
(créé par new / détruit par delete)
Création 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)) { // crée l'objet pointé par c
}
};
on pourrait aussi écrire :
Dessin(int x, int y, unsigned int w) :
b(x, y, w) {
c = new Square(x, y, w);
}
il faut rajouter dans le fichier .cpp
Square Dessin::a(10, 20, 300); // on ne repete pas "static"
Qu'est-ce qui manque ?
Destruction de l'objet
Il faut un destructeur !
chaque fois qu'un constructeur fait new (sinon fuites mémoires)
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;}
};
Remarques
b pas créé avec new => pas de delete
destructeurs généralement virtuels (pour polymorphisme)
Qu'est-ce qui manque ?
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);
Dessin d2(10, 20, 300);
d2 = d1; // affectation
Dessin d3 = d1; // initialisation
Dessin d4(d1); // idem (syntaxe equivalente)
}
Quel est le probleme ?
quand on sort de foo() ...
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);
Dessin d2(10, 20, 300);
d2 = d1; // affectation
Dessin d3 = d1; // initialisation
Dessin d4(d1); // idem
}
Le contenu de d1 est copié dans d2, d3 et d4 !
=> toutes les variables c pointent sur la même instance de Square
=> cette instance est détruite 4 fois (BOUM!) quand on sort de foo
(et les autres jamais)
Il faudrait de la copie profonde
Interdire la copie d'objets
La copie d'objets est dangereuse
s'ils contiennent des pointeurs ou des références !
1e solution : interdire la copie
déclarer privés l'opérateur d'initialisation (copy constructor)
et d'affectation (operator=)
implémentation inutile
interdit également la copie pour les sous-classes
class Dessin {
....
private:
Dessin(const Dessin&); // initialisation: Dessin a = b;
Dessin& operator=(const Dessin&); // affectation: a = b;
....
};
Solution similaire à Java
qui ne permet pas de copier un objet dans un autre en utilisant =
(en Java = ne peut copier que des types de base ou des références)
Redéfinir la copie d'objets (copie profonde)
2e solution : redéfinir la copie
l'opérateur d'initialisation et d'affectation font de la copie profonde
class Dessin : public Graphique {
....
public:
Dessin(const Dessin&); // initialisation
Dessin& operator=(const Dessin&); // affectation
....
};
Dessin::Dessin(const Dessin& from) : Graphique(from) {
b = from.b;
if (from.c != NULL) c = new Square(*from.c); // copie profonde
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); // copie profonde
else c = NULL;
return *this;
}
Compléments
Tableaux: new[ ] et delete[ ]
int* tab = new int[100];
delete [] tab; // ne pas oublier les []
tab = 0;
Ne pas mélanger les opérateurs !
x = new -> delete x
x = new[] -> delete[] x
x = malloc() -> free(x) // éviter malloc() et free()
Redéfinition de new et delete
possible, comme pour presque tous les opérateurs du C++
Méthodes virtuelles
méthodes virtuelles => destructeur virtuel
ne le sont plus dans les constructeurs / destructeurs !
Chapitre 4 : Constance
Variables "const"
ne peuvent pas changer de valeur
doivent obligatoirement être initialisées
Exemples
alternative aux #define :
const int MAX_ELEM = 200;
strcat( ) ne peut pas modifier le 2e argument :
char* strcat(char* s1, const char* s2);
les variables d'instance ne peuvent pas changer :
class User {
const int id;
const string name; // name contient l'objet
public:
// pas optimal (a suivre...)
User(int i, string n) : id(i), name(n)) {}
};
Pointeurs et littéraux
Pointeurs : le const porte sur ce qui suit :
l'objet pointé est constant
const char *s = "abcd";
*s = 'x'; // INTERDIT (par le compilateur)
s = "toto" // OK
le pointeur est constant
char* const s = "abcd"
*s = 'x'; // PERMIS (mais faux dans ce cas !!!)
s = "toto" // INTERDIT
pointeur et objet pointé constants
const char* const s = "abcd";
Attention aux littéraux (i.e. "abcd")
souvent en mémoire "read-only" => plantage si on modifie leur contenu!
écrire :
const char* s = "abcd";
pour éviter les erreurs
Méthodes "const"
Les méthodes d'instance const
ne modifient pas les variables d'instance
mais sont applicables sur des objets const
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; // OK : getX() est const
s2->setX(50); // INTERDIT! setX() n'est PAS const
Remarques
Un bon conseil !
mettre les const DES LE DEBUT de l'écriture du programme
changements pénibles a posteriori (modifs. en cascade...)
enum, une alternative a const pour les entiers
enum WEEK_END {SAMEDI=6, DIMANCHE=7};
Valeur de retour des fonctions
class User {
char * name; // chaine du C (a eviter!)
public:
const char* getName() const {return name;}
char* getName() {return name;}
};
renvoie directement la chaîne stockée par l'objet sans faire de copie
!!! la 2e version est DANGEREUSE (mais pas la 1ere) !!!
Conversion de constance
Exemple
on veut interdire la modification d'un objet
mais cet objet doit allouer une ressource à l'exécution
class DrawableSquare { // s'affiche a l'ecran
Peer* peer;
public:
DrawableSquare() : peer(0) {} // peer inconnu a ce stade
void draw() const {if (!peer) peer = createPeer();}
};
void foo() {
const DrawableSquare top_left(0,0,10); // ne doit pas bouger;
rect.draw(); // OK: draw() est const
}
Probleme ?
Conversion de constance
class DrawableSquare { // s'affiche a l'ecran
Peer* peer;
public:
DrawableSquare() : peer(0) {}
void draw() const {if (!peer) peer = createPeer();}
};
void foo() {
const DrawableSquare top_left(0,0,10);
rect.draw();
}
Probleme
draw( ) ne peut etre const car elle modifie 'peer' !
Solution ?
Cast et const_cast<>
(Très) mauvaise solution
void draw() const { // et si on se trompe de type ?
if (!peer)
((DrawableSquare*)this)->peer = createPeer();
}
(Moins) mauvaise solution
void draw() const { // verifie que c'est le meme type
if (!peer)
const_cast<DrawableSquare*>(this)->peer = createPeer();
}
Risque de plantage dans les 2 cas !
un objet const peut être stocké en mémoire "read-only"
Constance logique et constance physique
Bonne solution
class DrawableSquare {
mutable Peer* peer; // toujours modifiable !
public:
void draw() const {if (!peer) peer = createPeer();}
};
constance logique : point de vue du client
constance physique : implémentation, inconnue du client
Chapitre 5 : Passage par valeur et par référence
Passage par valeur
class MySocket {
public:
MySocket(const char* host, int port);
void send(int i);
....
};
void MySocket::send(int i) {
// envoie i sur la socket
}
void foo() {
MySocket sock("infres", 6666);
int a = 5;
sock.send(a);
}
Quelle est la relation entre l'argument a et le parametre i ?
Passage par valeur
class MySocket {
public:
MySocket(const char* host, int port);
void send(int i);
....
};
void MySocket::send(int i) {
// envoie i sur la socket
}
void foo() {
MySocket sock("infres", 6666);
int a = 5;
sock.send(a); // arg a copie dans param i
}
la valeur de l'argument est recopiée dans le paramètre de la fonction
sauf pour les tableaux (l'adresse du 1er élément est recopiée)
cas par défaut pour C++ et C# (seule possibilité pour C et Java)
Comment recupérer une valeur ?
class MySocket {
public:
MySocket(const char* host, int port);
void send(int i);
void receive(int i);
....
};
void MySocket::receive(int i) {
// recupere i depuis la socket
i = ...;
}
void foo() {
MySocket sock("infres", 6666);
int a;
sock.receive(a);
}
Que se passe t'il ?
Passage par référence
class MySocket {
public:
MySocket(const char* host, int port);
void send(int i);
void receive(int& i);
....
};
void MySocket::receive(int& i) {
i = ...; // recupere i depuis la socket
}
void foo() {
MySocket sock("infres", 6666);
int a;
sock.receive(a);
}
Passage par référence
pas de recopie : l'argument a et le paramètre i référencent la même entité
i est un "alias" de a => a est bien modifié au retour de la fonction
Attention: PAS de passage par reference en Java (contrairement aux apparences) !
Cas des "gros arguments"
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);
}
Quel est le probleme ?
Cas des "gros arguments"
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);
}
Problèmes
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
inconvénient : send() pourrait modifier a (ce qui n'a pas de sens)