IdentifiantMot de passe
Loading...
Mot de passe oubli� ?Je m'inscris ! (gratuit)
logo

FAQ C++Consultez toutes les FAQ

Nombre d'auteurs : 34, nombre de questions : 368, derni�re mise � jour : 14 novembre 2021  Ajouter une question

 

Cette FAQ a �t� r�alis�e � partir des questions fr�quemment pos�es sur les forums de http://www.developpez.com et de l'exp�rience personnelle des auteurs.

Je tiens � souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne pr�tend pas non plus �tre compl�te. Si vous trouvez une erreur ou si vous souhaitez devenir r�dacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.

SommaireProgrammation objet en C++ (23)
pr�c�dent sommaire suivant
 

Les techniques OO sont la meilleure fa�on connue de d�velopper de grosses applications ou des syst�mes complexes.

L'industrie du logiciel n'arrive pas � satisfaire les demandes pour des syst�mes logiciels aussi imposants que complexes, mais cet �chec est d� � nos succ�s : nos r�ussites ont habitu� les utilisateurs � toujours en demander plus. Malheureusement, nous avons ainsi cr�� une demande du march� que les techniques 'classiques' de programmation ne pouvaient satisfaire. Cela nous a oblig� � cr�er un meilleur paradigme.

Le C++ permet de programmer OO, mais il peut aussi �tre utilis� comme un langage classique (� un C am�lior� �). Si vous comptez l'utiliser de cette fa�on, n'esp�rez pas profiter des b�n�fices apport�s par la programmation OO.

Mis � jour le 10 f�vrier 2004 Cline

Une zone de stockage avec une s�mantique associ�e.
Apr�s la d�claration suivante,

Code c++ : S�lectionner tout
int i;
on peut dire que i est un objet de type int. En programmation objet / C++, � Objet � signifie habituellement � une instance d'une classe �. Une classe d�finit donc le comportement d'un ou plusieurs objets (c'est-ce qu'on peut appeler � instance �).

Mis � jour le 0 0 Cline

L'h�ritage consiste � construire une classe (appel�e classe fille) par sp�cialisation d'une autre classe (classe m�re). On peut illustrer ce principe en prenant l'exemple des mammif�res (classe m�re) et l'homme d'un c�t� (classe fille1) et les chiens (classe fille2). En effet, les chiens et les hommes sont tous deux des mammif�res mais ont des sp�cificit�s.

Mis � jour le 20 avril 2003 LFE

Il s'agit d'�viter des acc�s non autoris�s � certaines informations et/ou fonctionnalit�s.

L'id�e cl� est de s�parer la partie volatile de la partie stable. L'encapsulation permet de dresser un mur autour d'une partie du code, ce qui permet d'emp�cher une autre partie d'acc�der � cette partie dite volatile ; les autres parties du code ne peuvent acc�der qu'� la partie stable. Cela �vite que le reste du code ne fonctionne plus correctement lorsque le code volatile est chang�. Dans le cadre de la programmation objet, ces parties de code sont normalement une classe ou un petit groupe de classe.

Les � parties volatiles � sont les d�tails d'impl�mentation. Si le morceau de code est une seule classe, la partie volatile est habituellement encapsul�e en utilisant les mots-cl�s private et protected. S'il s'agit d'un petit groupe de classe, l'encapsulation peut �tre utilis�e pour interdire � des classes enti�res de ce groupe. L'h�ritage peut aussi �tre utilis� comme une forme d'encapsulation.

Les parties stables sont les interfaces. Une bonne interface procure une vue simplifi�e exprim�e dans le vocabulaire de l'utilisateur, et est cr��e dans l'optique du client. (un utilisateur, dans le cas pr�sent, signifie un autre d�veloppeur, non pas le client qui ach�tera l'application). Si le morceau de code est une classe unique, l'interface est simplement l'ensemble de ses membres publics et des fonctions amies. S'il s'agit d'un groupe de classes, l'interface peut inclure un certain nombre de classes.

Concevoir une interface propre et s�parer cette interface de son impl�mentation permet aux utilisateurs de l'utiliser convenablement. Mais encapsuler (mettre dans une capsule) l'impl�mentation force l'utilisateur � utiliser l'interface.

Mis � jour le 10 f�vrier 2004 Cline

Non.

L'encapsulation ne constitue pas un m�canisme de s�curit�. Il s'agit d'une protection contre les erreurs, pas contre l'espionnage.

Mis � jour le 10 f�vrier 2004 Cline

En g�n�ralisant le concept d'encapsulation.

Chaque classe ne propose � son utilisateur qu'un nombre minimal de fonctions publiques tr�s sp�cifiques et dont le comportement est clairement d�termin�. Chaque fonction publique fournie par une classe correspond � un service que l'on attend d'elle.

Ces fonctions d�finissent ce que l'on appelle l'interface de la classe en question.

Le r�sultat final est comme une � structure encapsul�e �. Cela am�liore le compromis entre fiabilit� (dissimulation de l'information) et facilit� d'utilisation (les instances multiples).

Mis � jour le 6 juillet 2014 koala01

J'applique une m�thode simple : la question � se poser est la suivante : est-ce que X est un genre de Y, ou est-ce que X utilise un Y ?
Si la r�ponse est X est un genre de Y, il s'agit d'un cas o� je d�rive une classe.
Si la r�ponse est X utilise Y, il s'agit d'un cas o� je vais encapsuler une classe.

Mis � jour le 9 octobre 2003 LFE

Quand elle pr�sente une vue simplifi�e d'un bout de logiciel, et est exprim�e dans les termes de l'utilisateur (le bout de logiciel correspond habituellement � une classe ou un petit groupe de classes et l'utilisateur est un autre d�veloppeur, non le client final).

� Vue simplifi�e � signifie que les d�tails sont intentionnellement cach�s. Cela r�duit donc le risque d'erreur lors de l'utilisation de la classe.

� Vocabulaire de l'utilisateur � veut dire que l'utilisateur n'a pas besoin d'apprendre de nouveaux mots ou concepts. Cela r�duit donc la courbe d'apprentissage de l'utilisateur.

Mis � jour le 10 f�vrier 2004 Cline

Un accesseur (accessor en anglais) est une fonction membre renvoyant la valeur d'une propri�t� d'un objet. Un mutateur (mutator en anglais) ou encore modifieur (modifier en anglais) est une fonction membre qui modifie la valeur d'une propri�t� d'un objet.

L'utilisation d'accesseurs / mutateurs permet de masquer l'impl�mentation des donn�es de la classe (encapsulation) et de faire �voluer celle-ci sans contraintes pour l'utilisateur final. Si ce dernier est oblig� de passer par des accesseurs / mutateurs au lieu d'acc�der directement aux donn�es internes, ces derni�res peuvent �tre chang�es � tout moment et il suffit alors d'adapter le code des accesseurs / mutateurs. Le code qui utilisait l'ancienne classe peut utiliser la nouvelle sans s'apercevoir des changements effectu�s, alors qu'un acc�s direct aux donn�es internes aurait n�cessit� de tout reprendre.
Les accesseurs / mutateurs permettent donc de s�parer l'utilisation des donn�es de leur impl�mentation, en plus de pouvoir effectuer des traitements ou des contr�les annexes lors de l'assignation des membres.
Dans l'exemple suivant :

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person 
{ 
public: 
    // accesseur : renvoie le nom 
    const std::string & GetName() const // notez le const 
    { 
        return name; 
    } 
  
    // mutateur : change le nom 
    void SetName( const std::string & NewName ) 
    { 
        name = NewName; 
    } 
  
private: 
    std::string name; // nom de la personne 
};
GetName est un accesseur car elle renvoie la valeur de la propri�t� name. SetName est un mutateur car il modifie la valeur de la propri�t� name.
Comme le montre cet exemple, il est courant de pr�fixer le nom des accesseurs / mutateurs respectivement par Get / Set. Pour cette raison, on appelle aussi les accesseurs / mutateurs des getter / setter.

Les accesseurs ne modifiant pas l'objet mais se contentant de fournir un acc�s (d'o� leur nom) en lecture seule sur une de ses propri�t�s, c'est une bonne pratique que de rendre une telle fonction membre constante comme cela est le cas ici pour GetName (lire � ce sujet Pourquoi certaines fonctions membres poss�dent le mot cl� const apr�s leur nom ?.
Un point important est que les accesseurs / mutateurs ne s'appliquent pas forc�ment sur des donn�es membres existantes d'une classe, mais peuvent �tre utilis�s pour simuler l'existence d'une propri�t� qui n'est pas directement stock�es en interne dans la classe. Lire � ce sujet Quand et comment faut-il utiliser des accesseurs / mutateurs ?.

Mis � jour le 22 novembre 2004 Aurelien.Regat-Barrel Luc Hermitte

Parmi les fonctions publiques d'une classe, certaines miment la pr�sence d'une donn�e membre. On nomme aussi de telles fonctions des accesseurs. Il n'y a pas forc�ment de relation un-pour-un entre un accesseur et une donn�e membre (comme cela est le cas pour l'accesseur GetName et la variable name dans l'exemple de la question Que sont les accesseurs / mutateurs ?. Une donn�e encapsul�e ne doit pas forcement �tre expos�e via � un accesseur. L'�tat interne d'un objet est� interne, et doit le rester.
Il faut distinguer deux choses lorsque l'on �crit une classe : son interface et son impl�mentation. Le but des accesseurs / mutateurs est d'effectuer le lien entre les deux, lien qui n'a pas � �tre direct. L'interface, qui sera visible du reste du monde et qui est donc la premi�re chose � d�terminer quand on �crit une classe, expose un certain nombre de propri�t�s, qui peuvent ou non �tre directement stock�es dans la classe. Ce dernier point est un d�tail d'impl�mentation qui n'a pas � �tre connu, et c'est le r�le des accesseurs / mutateurs de le masquer.
Prenons l'exemple d'une classe qui permet de conna�tre l'�ge d'un individu :

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
#include "date.h" // classe permettant de stocker une date (pour l'exemple) 
  
class Person 
{ 
public: 
    // �ge de la personne 
    int GetAge() const; 
  
private: 
    // date de naissance 
    Date  date_of_birth; 
};
L'�ge d'une personne �volue constamment au fil du temps, c'est pourquoi il a �t� d�cid� dans cet exemple de ne pas le stocker mais de conserver � la place sa date de naissance. L'accesseur GetAge se charge de calculer son �ge courant � partir de sa date de naissance et de la date du jour. Ainsi nous avons bien un accesseur sur la propri�t� Age de la classe, mais il n'y a pas de transposition directe sous forme de donn�e membre int age; pour autant. On utilise � la place une autre donn�e membre : la date de naissance. S'agissant d'un d�tail d'impl�mentation, aucun accesseur n'existe pour renvoyer cette derni�re.
Cet exemple illustre bien le fait qu'un accesseur exporte une propri�t� qui n'a nullement l'obligation d'exister de mani�re explicite dans la classe. De m�me, une variable membre ne doit pas forc�ment �tre export�e via un accesseur, comme dans cet exemple avec la date de naissance.
Un autre exemple typique est celui de la classe Temperature qui permet de manipuler des temp�ratures en degr�s Celsius ou Fahrenheit :

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Temperature 
{ 
public: 
    // degr�s Celsius 
    double GetCelsius() const 
    { 
        return this->temp_celsius; 
    } 
    void SetCelsius( double NewTemp ) 
    { 
        this->temp_celsius = NewTemp; 
    } 
  
    // degr�s Fahrenheit 
    double GetFahrenheit() const 
    { 
        return ( ( this->temp_celsius * 9.0 ) / 5.0 ) + 32.0; 
    } 
    void SetFahrenheit( double NewTemp ) 
    { 
        this->temp_celsius = ( NewTemp - 32.0 ) * 5.0 / 9.0; 
    } 
  
private: 
    // en interne, on stocke en degr�s Celsius 
    double temp_celsius; 
};
D'un point de vue logique il y a deux propri�t�s : Celsius et Fahrenheit. Mais en interne il n'y a qu'une seule donn�e membre. Imaginons maintenant que l'utilisation de cette classe montre que la plupart du temps on manipule les temp�ratures en degr�s Fahrenheit, ce qui a chaque fois n�cessite de faire un calcul de conversion. On d�cide alors de changer l'impl�mentation de la classe pour stocker directement en Fahrenheit, ce qui donne :

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Temperature 
{ 
public: 
    // degr�s Celsius 
    double GetCelsius() const 
    { 
        return ( this->temp_fahrenheit - 32.0 ) * 5.0 / 9.0; 
    } 
    void SetCelsius( double NewTemp ) 
    {         
        this->temp_fahrenheit = ( ( NewTemp * 9.0 ) / 5.0 ) + 32.0; 
    } 
  
    // degr�s Fahrenheit 
    double GetFahrenheit() const 
    { 
        return this->temp_fahrenheit; 
    } 
    void SetFahrenheit( double NewTemp ) 
    { 
        this->temp_fahrenheit = NewTemp; 
    } 
  
private: 
    // en interne, on stocke en degr�s Fahrenheit 
    double temp_fahrenheit; 
};
Comme on peut le constater, cette nouvelle impl�mentation est sans cons�quence d'un point de vue logique sur la classe. Son interface est intacte, ce qui la rend inchang�e vis � vis de l'ext�rieur. Pourtant en interne il a �t� fait des modifications qui la rendent plus performante. C'est un des int�r�ts des accesseurs : s'adapter de fa�on transparente aux �volutions de l'impl�mentation, chose que l'on ne peut pas garantir avec des donn�es membre publiques.
Vous l'aurez compris : le choix de d�finir des accesseurs / mutateurs doit �tre en accord avec la conception et l'analyse du probl�me. Il ne faut pas syst�matiser leur d�finition pour toutes les donn�es membres d'une classe.

Mis � jour le 22 novembre 2004 Aurelien.Regat-Barrel JolyLoic Luc Hermitte

Par l'ext�rieur !

Une bonne interface fournit une vue simplifi�e exprim�e dans le vocabulaire de l'utilisateur. Dans le cas de la programmation par objets, une interface est g�n�ralement repr�sent�e par une classe unique ou par un groupe de classes tr�s proches.

R�fl�chissez d'abord � ce qu'un objet de la classe est du point de vue logique, plut�t que de r�fl�chir � la fa�on dont vous allez le repr�senter physiquement. Imaginez par exemple que vous ayez une classe Stack (une pile) et que vous vouliez que son impl�mentation utilise une LinkedList (une liste cha�n�e)

Code c++ : S�lectionner tout
1
2
3
4
5
6
class Stack { 
   public: 
      // ... 
   private: 
      LinkedList list_; 
};
La classe Stack doit-elle avoir une fonction membre get() qui retourne la LinkedList ? Ou une fonction set() qui prenne une LinkedList ? Ou encore une constructeur qui prenne une LinkedList ? La r�ponse est �videmment non, puisque la conception d'une classe doit s'effectuer de l'ext�rieur vers l'int�rieur. Les utilisateurs des objets Stack n'ont rien � faire des LinkedLists ; ce qui les int�resse, c'est de pouvoir faire des push (empiler) et des pop (d�piler).

Voyons maintenant un cas un peu plus subtil. Supposez que l'impl�mentation de la classe LinkedList soit bas�e sur une liste cha�n�e d'objets Node (nouds), et que chaque Node ait un pointeur sur le Node suivant :

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
class Node  
{  
   /*...*/  
}; 
  
class LinkedList { 
    public: 
       // ... 
    private: 
       Node* first_; 
};
La classe LinkedList doit-elle avoir une fonction get() qui donne acc�s au premier Node ? L'objet Node doit-il avoir une fonction get() qui permette aux utilisateurs de passer au Node suivant dans la cha�ne ? La question est en fait : � quoi une LinkedList doit-elle ressembler vu de l'ext�rieur ? Une LinkedList est-elle vraiment une cha�ne d'objets Node ? Ou cela n'est-il finalement qu'un d�tail d'impl�mentation ? Et si c'est juste un d�tail d'impl�mentation, comment la LinkedList va-t-elle donner � ses utilisateurs la possibilit� d'acc�der � chacun de ses �l�ments ?

Une r�ponse parmi d'autres : une LinkedList n'est pas une cha�ne d'objets Nodes. C'est peut-�tre bien comme �a qu'elle est impl�ment�e, mais ce n'est pas ce qu'elle est. Ce qu'elle est, c'est une suite d'�l�ments. L'abstraction LinkedList doit donc �tre fournie avec une classe � LinkedListIterator �, et c'est cette classe � LinkedListIterator � qui doit disposer d'un operator++ permettant de passer � l'�l�ment suivant, ainsi que de fonctions get()/set() donnant acc�s � la valeur stock�e dans un Node (la valeur stock�e dans un Node est sous l'unique responsabilit� de l'utilisateur de la LinkedList, c'est pourquoi il faut des fonctions get()/set() permettant � cet utilisateur de la manipuler comme il l'entend).

Toujours du point de vue de l'utilisateur, il pourrait �tre souhaitable que la classe LinkedList offre un moyen d'acc�der � ses �l�ments qui mimique la fa�on dont on acc�de aux �l�ments d'un tableau en utilisant l'arithm�tique des pointeurs :

Code c++ : S�lectionner tout
1
2
3
4
5
void userCode(LinkedList& a) 
{ 
    for (LinkedListIterator p = a.begin(); p != a.end(); ++p) 
        cout << *p << '\n'; 
}
Pour impl�menter cette interface, la LinkedList va avoir besoin d'une fonction begin() et d'une fonction end(). Ces fonctions devront renvoyer un objet de type � LinkedListIterator �. Et cet objet � LinkedListIterator � aura lui besoin : d'une fonction pour se d�placer vers l'avant (de fa�on � pouvoir �crire ++p); d'une fonction pour pouvoir acc�der � la valeur de l'�l�ment courant (de fa�on � pouvoir �crire *p); et d'un op�rateur de comparaison (de fa�on � pouvoir �crire p != a.end()).

Le code se trouve ci-dessous. L'id�e centrale est que la classe LinkedList n'a pas de fonction donnant acc�s aux Nodes. Les Nodes sont une technique d'impl�mentation, technique qui est compl�tement masqu�e. Les internes de la classe LinkedList pourraient tout � fait �tre remplac�s par une liste doublement cha�n�e, ou m�me par un tableau, avec pour seule diff�rence une modification au niveau de la performance des fonctions prepend(elem) et append(elem).

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <cassert>   // Succ�dan� de gestion d'exceptions 
  
class LinkedListIterator; 
class LinkedList; 
  
class Node { 
    // Pas de membres public, c'est une � classe priv�e � 
    friend LinkedListIterator;  // Une classe amie  
    friend LinkedList; 
    Node* next_; 
    int elem_; 
}; 
  
class LinkedListIterator { 
   public: 
      bool operator== (LinkedListIterator i) const; 
      bool operator!= (LinkedListIterator i) const; 
      void operator++ ();  // Aller � l'�l�ment suivant 
      int& operator*  ();  // Acc�der � l'�l�ment courant 
   private: 
      LinkedListIterator(Node* p); 
      Node* p_; 
}; 
  
class LinkedList { 
   public: 
      void append(int elem);   // Ajoute elem apr�s le dernier �l�ment 
      void prepend(int elem);  // Ajoute elem avant le premier �l�ment 
      // ... 
      LinkedListIterator begin(); 
      LinkedListIterator end(); 
      // ... 
   private: 
      Node* first_; 
};
Les fonctions membres suivantes sont de bonnes candidates pour �tre inline (� mettre sans doute dans le m�me .h):

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
inline bool LinkedListIterator::operator== (LinkedListIterator i) const 
{ 
      return p_ == i.p_; 
} 
  
inline bool LinkedListIterator::operator!= (LinkedListIterator i) const 
{ 
      return p_ != i.p_; 
} 
  
inline void LinkedListIterator::operator++() 
{ 
      assert(p_ != NULL);  // ou bien if (p_==NULL) throw ... 
      p_ = p_->next_; 
} 
  
inline int& LinkedListIterator::operator*() 
{ 
      assert(p_ != NULL);  // ou bien if (p_==NULL) throw ... 
      return p_->elem_; 
} 
  
inline LinkedListIterator::LinkedListIterator(Node* p) 
      : p_(p) 
{  
} 
  
inline LinkedListIterator LinkedList::begin() 
{ 
      return first_; 
} 
  
inline LinkedListIterator LinkedList::end() 
{ 
      return NULL; 
}
Pour conclure : la liste cha�n�e g�re deux sortes de donn�es diff�rentes. On trouve d'un c�t� les valeurs des �l�ments qui sont stock�s dans la liste cha�n�e. Ces valeurs sont sous la responsabilit� de l'utilisateur de la liste et seulement de l'utilisateur. La liste elle-m�me ne fera rien par exemple pour emp�cher � un utilisateur de donner la valeur 5 au troisi�me �l�ment, m�me si �a n'a pas de sens dans le contexte de cet utilisateur. On trouve de l'autre c�t� les donn�es d'impl�mentation de la liste (pointeurs next, etc.), dont les valeurs sont sous la responsabilit� de la liste et seulement de la liste, laquelle ne donne aux utilisateurs aucun acc�s (que ce soit en lecture ou en �criture) aux divers pointeurs qui composent son impl�mentation.

Ainsi, les seules fonctions get()/set() pr�sentes sont l� pour permettre la modification des �l�ments de la liste cha�n�e, mais ne permettent absolument pas la modification des donn�es d'impl�mentation de la liste. Et la liste cha�n�e ayant compl�tement masqu� son impl�mentation, elle peut donner des garanties tr�s fortes concernant cette impl�mentation (dans le cas d'une liste doublement cha�n�e par exemple, la garantie pourrait �tre qu'il y a pour chaque pointeur avant, un pointeur arri�re dans le Node suivant).

Nous avons donc vu un exemple dans lequel les valeurs de certaines des donn�es d'une classe �taient sous la responsabilit� des utilisateurs de la classe (et la classe a besoin d'exposer des fonctions get()/set() pour ces donn�es) mais dans lequel les donn�es contr�l�es uniquement par la classe ne sont pas n�cessairement accessibles par des fonctions get()/set().

Note : le but de cet exemple n'�tait pas de vous montrer comment �crire une classe de liste cha�n�e. Et d'abord, vous ne devriez pas � pondre � votre propre classe liste, vous devriez plut�t utiliser l'une des classes de type � conteneur standard � fournie avec votre compilateur. La meilleure solution est d'utiliser l'une des classes conteneurs du standard C++, par exemple la classe template list<T>.

Mis � jour le 30 ao�t 2004 Cline

Le polymorphisme, c'est la capacit� d'une expression � �tre valide quand les valeurs pr�sentes ont des types diff�rents. On trouve diff�rents types de polymorphismes :

  • ad-hoc�: surcharge et coercition ;
  • universel (ou non ad-hoc)�: param�trique et d'inclusion.
Ces deux distinctions segmentent le polymorphisme suivant l'axe de r�utilisabilit� face � un nouveau type : le polymorphisme ad-hoc n�cessite une nouvelle d�finition pour chaque nouveau type alors que le polymorphisme universel recouvre un ensemble potentiellement illimit� de types.

Mis � jour le 15 octobre 2009 3DArchi Jean-Marc.Bourguet

Comme Mr Jourdain �crivait de la prose sans le savoir, vous avez certainement d�j� utilis� la coercition sans le savoir. Derri�re cette expression se cache tout simplement les m�canismes de conversion implicite :

Code c++ : S�lectionner tout
1
2
3
int op1(1); 
double op2(2.1); 
double result = op1 + op2; // polymorphisme de coercition
Ici, op1 est implicitement converti en double pour faire l'op�ration d'addition.
Une classe s'appuie sur la d�finition d'op�rateur de conversion pour pouvoir �tre utilis�e dans ce type de polymorphisme :

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream> 
  
class CMyClass 
{ 
public: 
   operator bool()const 
   { 
      return true; 
   } 
}; 
  
int main() 
{ 
   CMyClass a; 
   std::cout << std::boolalpha << a << std::endl; 
   return 0; 
}
L'objet 'a' est implicitement converti en bool. Le code pr�sent� ici illustre la d�finition. Il est en g�n�ral d�conseill� d'utiliser ce m�canisme (cf bool idiom), les cas pertinents demeurant rares (enveloppes sur des handles CWnd<->HWND par exemple).

Mis � jour le 15 octobre 2009 3DArchi

Le polymorphisme param�trique passe par l'utilisation des techniques g�n�riques pour offrir un m�me service pour tout un ensemble de types :

Code c++ : S�lectionner tout
1
2
3
4
5
template<class T> 
void dump(T var) 
{ 
   std::cout << Timestamp() << " : " << var << std::endl; 
}
Cette op�ration peut �tre appel�e pour n'importe quel type d'objet du moment qu'il supporte l'op�rateur << sur le flux de sortie. La plupart des biblioth�ques modernes en C++ s'appuient sur ce m�canisme. C'est le cas de la STL ou de Boost par exemple.
On parle parfois de polymorphisme contraint ou born� lorsqu'il s'agit d'introduire des contraintes sur les types avec lesquels une fonction ou une classe g�n�rique peut effectivement �tre instanci�e. Cela est possible avec le C++ en combinant les classes traits et des biblioth�ques comme std::enable_if ou static_assert. La notion de concept a pour but d'�tendre cette notion de contraintes. Un TS (technical specification, une sorte de version beta d'un standard) sur ce sujet a m�me �t� accept�.

Mis � jour le 15 octobre 2009 3DArchi JolyLoic

Souvent r�sum� tout simplement (et trop hativement) � ��polymorphisme��, le polymorphisme d'inclusion s'appuie sur l'h�ritage public :

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void function(IInterface const &var_) 
{ 
   var_.Action(); 
} 
class IInterface 
{ 
    // ... 
}; 
class CConcrete : public IInterface 
{ 
    // ... 
}; 
  
int main() 
{ 
    CConcrete c; 
    Fonction(c); 
    return 0; 
}
Les fonctions virtuelles utilisent bien s�r ce polymophisme.
Le polymorphisme d'inclusion doit faire sens avec l'h�ritage public. Il ne doit pas �tre utilis� uniquement pour b�n�ficier d'une surcharge.

Mis � jour le 15 octobre 2009 3DArchi

Il s'agit simplement en fait d'introduire un point de variabilit� dans votre code, de faire en sorte que selon <on ne sait pas trop quoi>, le comportement de ce morceau de code soit diff�rent.
Le comportement d'un code C++ peut �tre param�tr� de diff�rentes fa�ons :

  • pendant l'�criture du code : les templates ;
  • � la compilation : surcharges, conversions implicites et directives de compilation ;
  • � l'�dition des liens ;
  • � l'ex�cution par le chargement dynamique de biblioth�que ;
  • � l'ex�cution par le polymorphisme d'inclusion (fonctions virtuelles).

Mis � jour le 15 octobre 2009 3DArchi Alp

En C++, on utilise souvent l'h�ritage pour ce faire. En effet, imaginez que nous soyons en pr�sence d'une hi�rarchie de composants graphiques, dont la classe de base serait Widget. On aurait ainsi Button et Textfield qui h�riteraient de Widget par exemple. Enfin, chacun poss�derait une m�thode show() qui permet d'afficher le composant en question. Bien entendu, un Button et un Textfield �tant de natures diff�rentes, leur affichage le serait aussi.
C'est gr�ce au polymorphisme d'h�ritage, mis en ouvre en C++ gr�ce au mot cl� virtual, que l'on peut r�aliser cela dynamiquement : � l'ex�cution du programme, il sera choisi d'utiliser la m�thode Button::show() ou la m�thode Textfield::show() selon le type r�el de l'objet sur lequel on appelle show(). Voici un exemple minimal illustrant cela.

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Widget 
{ 
  public: 
  virtual ~Widget() { /* ... */ } 
  void show() 
  { 
    // ... 
    do_show(); 
    // ... 
  } 
  // ... 
  
  private :  
    virtual void do_show()=0; // fonction virtuelle pure 
}; 
  
class Button : public Widget 
{ 
  private :  
    virtual void do_show() { std::cout << "Button" << std::endl; } 
  // ... 
}; 
  
class Textfield : public Widget 
{ 
  private :  
    virtual void do_show() { std::cout << "Textfield" << std::endl; } 
  // ... 
}; 
  
void show_widget(Widget& w) 
{ 
    w.show(); 
} 
  
// ... 
  
Button b; 
Textfield t; 
  
show_widget(b); // affiche "Button" 
show_widget(t); // affiche "Textfield"
Dans ce cas, rien � redire, vous avez fait un choix correct.

Mis � jour le 15 octobre 2009 3DArchi Alp

Imaginez que vous ayez con�u une classe qui encapsule un calcul tr�s lourd, au point que vous ayez mis sur pieds deux impl�mentations, l'une monothread�e, l'autre multithread�e. Il serait dommage de les faire h�riter d'une classe abstraite et d'en h�riter pour chacune des versions, induisant un co�t � cause de la virtualit�, qui est ici superflue. Vous avez une possibilit� qui vous permettra de tout g�rer � la compilation, en utilisant les templates. Nous allons illustrer avec une fonction qui mesure le temps pris par le calcul.

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
template <class Computation> 
void do_something()  
{  
    std::time_t start = time(NULL); 
    Computation::compute(); 
    std::time_t end = time(NULL); 
    std::cout << end - start << " seconds." << std::endl; 
} 
  
struct SingleThreadedComputation 
{ 
    static void compute() 
    {  
        // impl�mentation monothread 
    } 
}; 
  
struct MultiThreadedComputation 
{ 
    static void compute() 
    {  
        // impl�mentation multithread 
    } 
}; 
  
// par exemple : 
#ifdef STCOMPUTATION 
do_something<SingleThreadedComputation>(); 
#elif defined MTCOMPUTATION 
do_something<MultiThreadedComputation>(); 
#endif 
  
// comportement que l'on peut choisir soit avec un #define,  
// soit avec l'option de compilation -DSTCOMPUTATION ou -DMTCOMPUTATION
Ainsi, on a factoris� la variabilit� (multithreading ou pas) de notre calcul dans une fonction param�tr�e, Compute, sans ajouter la surcharge induite par l'utilisation de la virtualit�. Le � d�faut � est que la variabilit� est statique, c'est-�-dire fix�e � la compilation, tandis que le polymorphisme li� � l'h�ritage nous permet d'avoir une variabilit� � l'ex�cution.
Cette fa�on de faire s'approche de ce que l'on appelle le Policy Based Design, qui permet de param�trer de mani�re tr�s flexible le comportement, d�s la compilation, avec une utilisation intelligente des templates.
C'est une sorte d'�quivalent du Design Pattern Strategy, � la sauce C++ et templates.

Mis � jour le 15 octobre 2009 3DArchi Alp

Ensuite vient le polymorphisme issu de la manipulation du pr�processeur de votre compilateur. En effet, en jouant avec les #ifdef, nous pouvons par exemple s�lectionner un certain code ou un autre selon des directives de compilation, qui permettent de modifier le comportement de l'application g�n�r�e, et ce au moment de compiler. Cela se base sur le sch�ma basique suivant (qui a �t� utilis� pour l'exemple de calcul mono|multithread) :

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
#ifdef OPTION1 
  // code 1 
#elif defined OPTION2 
  // code 2 
#elif defined OPTION3 
  // code 3 
#else 
  // code 4 
#endif
Ainsi, vous pouvez s�lectionner le code � utiliser de deux fa�ons principalement. Soit vous �crivez dans votre programme principal quelque chose comme #define OPTION3, soit vous passez l'option -DOPTION3 � votre compilateur. Si aucune option n'est pass�e, ici ce sera le code 4 qui sera s�lectionn�, par exemple.

Mis � jour le 15 octobre 2009 3DArchi Alp

Vous pouvez �galement obtenir du polymorphisme en jouant sur la liaison avec des biblioth�ques. A partir d'une m�me interface, vous pouvez avoir diff�rentes impl�mentations produisant des biblioth�ques statiques diff�rentes (.lib, .a�). La commande d'�dition des liens (ou dans votre makefile ou dans les options d'un projet avec un I.D.E.) pr�cise la biblioth�que avec laquelle les liens doivent �tre r�solus. L'ex�cutable g�n�r� fait alors appel � l'interface impl�ment�e dans la biblioth�que avec laquelle il a �t� li�e.

Mis � jour le 15 octobre 2009 3DArchi Alp

Une application peut choisir de varier son comportement en chargeant dynamiquement des biblioth�ques (.dll, .so�) et en allant chercher dans celles-ci l'impl�mentation de l'interface variable. Le comportement va alors changer selon la DLL propos�e � l'ex�cution du moment qu'elle respecte l'interface qu'attend le programme.

Mis � jour le 15 octobre 2009 3DArchi Alp

S'il est n�cessaire de pouvoir modifier le comportement d'un objet au cour de l'ex�cution, la solution la plus adapt�e est sans doute l'application du design pattern Strategy.
Le principe de ce patron de conception est de d�finir autant de classes que de comportements diff�rents. Toutes ces classes impl�mentent une m�me interface. La classe � param�trer poss�de une agr�gation vers un objet du type de l'interface.

Code c++ : S�lectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Interface de comportement pour baladeur (utilisant le pattern NVI) 
class walkman_behaviour 
{ 
    public: 
        virtual 
        ~walkman_behaviour(){} 
  
        void 
        click_back_button() 
        { 
            do_click_back_button(); 
        } 
  
        void 
        click_forward_button() 
        { 
            do_click_forward_button(); 
        } 
  
    private: 
        virtual 
        void 
        do_click_back_button() = 0; 
  
        virtual 
        void 
        do_click_forward_button() = 0; 
}; 
  
// Collection de comportements pour baladeur 
namespace walkman_behaviours 
{ 
    class mp3_reader: public walkman_behaviour 
    { 
        public: 
            void 
            do_click_back_button() 
            { 
                // Lire chanson pr�c�dente... 
            } 
  
            void 
            do_click_forward_button() 
            { 
                // Lire chanson suivante... 
            } 
    }; 
  
    class fm_tuner: public walkman_behaviour 
    { 
        public: 
            void 
            do_click_back_button() 
            { 
                // Passer � la fr�quence pr�c�dente... 
            } 
  
            void 
            do_click_forward_button() 
            { 
                // Passer � la fr�quence suivante... 
            } 
    }; 
} 
  
//baladeur audio 
class walkman 
{ 
    public: 
        walkman(walkman_behaviour& c): 
            behaviour_(&c) 
        { 
        } 
  
        void 
        behaviour(walkman_behaviour& c) 
        { 
            behaviour_ = &c; 
        } 
  
        void 
        click_back_button() 
        { 
            comportement_->click_back_button(); 
        } 
  
        void 
        click_forward_button() 
        { 
            comportement_->click_forward_button(); 
        } 
  
    private: 
        walkman_behaviour* behaviour_; 
}; 
  
int main() 
{ 
    walkman_behaviours::mp3_reader behave_mp3; 
    walkman_behaviours::fm_tuner behave_fm; 
  
    walkman b(behave_mp3); // Comportement par d�faut : lecteur mp3 
    b.click_forward_button(); // Lit la chanson suivante 
  
    b.behaviour(behave_fm); // Changement de comportement 
    b.click_back_button(); // Passe � la fr�quence pr�c�dente 
  
    return 0; 
}
Pour changer le comportement de l'objet, il suffit de l'agr�ger � un autre objet de comportement.

Mis � jour le 15 octobre 2009 Florian Goo

Il faut d�sormais choisir celui qui convient au type de param�trage de comportement que vous voulez introduire. Une application complexe met souvent en ouvre les diff�rentes solutions pour sa variabilit� et son extension. Selon les cas, la variabilit� est int�gr�e par les templates (g�n�rique), par l'h�ritage (ex. : Widget), par les directives de compilation ou l'�dition statique de liens (ex. : d�pendance de plateforme), ou par le chargement dynamique de biblioth�que (ex. : plugin).

Mis � jour le 15 octobre 2009 3DArchi Alp

Proposer une nouvelle r�ponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plut�t sur le forum de la rubrique pour �a


R�ponse � la question

Liens sous la question
pr�c�dent sommaire suivant
 

Les sources pr�sent�es sur cette page sont libres de droits et vous pouvez les utiliser � votre convenance. Par contre, la page de pr�sentation constitue une �uvre intellectuelle prot�g�e par les droits d'auteur. Copyright � 2025 Developpez Developpez LLC. Tous droits r�serv�s Developpez LLC. Aucune reproduction, m�me partielle, ne peut �tre faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'� trois ans de prison et jusqu'� 300 000 � de dommages et int�r�ts.