dimanche 21 octobre 2012

Surdéfinition d’opérateurs C++


Comment ça marche

Nous avons commencé à réaliser une classe Point qui permet la recopie d’objet. Mais jusqu’à maintenant, nous avons été obligés de créer des méthodes telles que "Coincide" qui vérifie que deux points sont égaux. Nous aurions pu également réaliser une fonction membre qui ajoute un point à un autre :
class Point
{
     int x;
     int y;
public :
     Point();
     Point(int a, int b);
     Point(const Point & pt);
     void Ajoute(const Point & pt);
     ... // D’éventuelles autres méthodes 
};
Vous sentez alors qu’il serait très appréciable et plus naturel de pouvoir faire quelque chose comme :
     Point a(1,2);
     Point b(3,4);
     Point c;
     c = a + b;
C++ permet de réaliser une surdéfinition des opérateurs de base, comme "+", "-", "*", "/", "&", "^", etc. La liberté étant totale, vous pouvez faire réellement ce que vous voulez, par exemple une soustraction pour l’opérateur d’addition et inversement. Mais il est clair qu’il est plus que conseillé de respecter la signification de chacun de ces opérateurs.

Opérateurs simples

Mettre en oeuvre les opérateurs simples est une opération assez rapide. Un exemple est utile pour vous montrer comment on fait :
class Point
{
     int x;
     int y;
public :
     Point();
     Point(int a, int b);
     Point(const Point & pt);
     Point operator+(const Point & a)
     {
          Point p;
          p.x = a.x + x;
          p.y = a.y + y;
        return p;
     }
     ... // D’éventuelles autres méthodes 
};
Des explications sont nécessaires. Tout d’abord, vous remarquerez le "const Point & a". Ceci signifie que l’on passe un point en paramètre (l’autre point de l’addition est en fait la classe appelante elle-même). Ce dernier est transmis par référence, afin d’éviter une recopie, lourde et moins rapide. Le "const" est optionnel mais permet d’éviter de modifier les paramètres et également autorise l’utilisation d’objets constants.
L’opérateur rend un Point. En fait, on rend en fin de méthode le point qui a été créé temporairement au départ et qui contient la somme des deux paramètres. Plus exactement, on rend une copie de cet objet, le retour de la fonction étant "Point", et non "Point&" ou "Point*". Ceci est normal. Il faut savoir que l’objet créé dans la méthode sera automatiquement détruit à la fin de cette dernière. On ne peut bien évidemment pas rendre l’adresse d’un objet qui sera détruit ! D’où la nécessité d’une recopie.
Ce que nous avons mis en oeuvre pour l’opérateur d’addition, nous pouvons en faire de même pour tous les autres opérateurs "simples". Quelques exemples :
class Point
{
 int x;
 int y;
public :
 Point(){}
 Point(int a, int b){ x=a; y=b; }
 Point operator +(const Point & a);
 Point operator -(const Point & a);
 int operator==(const Point & p);
 void Affiche()
 {
  // "this" est un pointeur sur la classe même 
  cout << this << "->" << x << ", " << y << endl;
 }
};
Point Point::operator +(const Point & a)
// Addition de 2 points 
 Point p;
 p.x = x + a.x;
 p.y = y + a.y;
 return p;
}
Point Point::operator -(const Point & a)
// Soustraction de 2 points 
 Point p;
 p.x = x - a.x;
 p.y = y - a.y;
 return p;
}
int Point::operator==(const Point & p)
// Egalité de 2 points (remplace "Coincide") 
 if( x==p.x && y==p.y )
  return 1;
 else
  return 0;
}
void main()
{
 Point p(1,2);
 p.Affiche();
 Point pp(3,4);
 pp.Affiche();
 Point ppp = p+pp;
 ppp.Affiche();
 if( p==pp )
  cout << "p==pp" << endl;
 else
  cout << "p!=pp" << endl;
 p = ppp;
 p.Affiche();
 pp = p-ppp;
 pp.Affiche();
 if( p==ppp )
  cout << "p==ppp" << endl;
 else
  cout << "p!=ppp" << endl;
}

Opérateur d’affectation

L’opérateur d’affectation "=" représente un cas particulier. En effet, nous retrouvons le même problème que lors de la construction par recopie : il est toujours possible d’effectuer une affectation entre deux objets (de même type), mais que se passe-t-il s’ils contiennent des pointeurs (cf. problématique de recopie) ! C’est pourquoi il est souvent important d’implémenter ce type d’opérateur.
Il n’est pas plus difficile à mettre en oeuvre :
#include <iostream.h>
class Vecteur
{
 int *pVecteur;
 int nTaille;
public :
 Vecteur(int);
 Vecteur(const Vecteur &);
 ~Vecteur();
 int GetAt(int ind)
 {
  if( ind>=0 && ind<nTaille )
   return pVecteur[ind];
  else
   return 0;
 }
 void SetAt(int ind, int val)
 {
  if( ind>=0 && ind<nTaille )
   pVecteur[ind]=val;
 }
 int Size(){ return nTaille; }
 Vecteur& operator =(const Vecteur & v);
};
Vecteur::Vecteur(int Taille)
{
 nTaille = Taille;
 pVecteur = new int[nTaille];
}
Vecteur::Vecteur(const Vecteur & v)
{
 nTaille = v.nTaille;
 pVecteur = new int[nTaille];
 forint i=0; i<nTaille; i++ )
  pVecteur[i]=v.pVecteur[i];
}
Vecteur::~Vecteur()
{
 delete []pVecteur;
}
Vecteur& Vecteur::operator =(const Vecteur & v)
{
 // On vérifie que les objets ne sont pas les mêmes ! 
 if( this != &v )
 {
  delete []pVecteur; // Effacement du vecteur 
  nTaille = v.nTaille;
  pVecteur = new int[nTaille]; // Allocation 
  // Recopie des valeurs 
  forint i=0; i<nTaille; i++ )
   pVecteur[i]=v.pVecteur[i];
 }
 return *this;
}
void main()
{
 Vecteur *v;
 v = new Vecteur(10);
 forint i=0; i<v->Size(); i++ )
  v->SetAt(i, i*i-i);
 Vecteur vv(5);
 vv = *v;
 vv.SetAt(0, 13);
 vv.SetAt(1, 13);
 vv.SetAt(2, 13);
 for( i=0; i<v->Size(); i++ )
  cout << v->GetAt(i) << " ";
 cout << endl;
 for( i=0; i<vv.Size(); i++ )
  cout << vv.GetAt(i) << " ";
 cout << endl;
 delete v;
}
Cette fois-ci, nous rendons par contre une référence sur l’objet, car nous devons rendre la classe elle-même et non une copie, comme vous pouvez le comprendre.

Aucun commentaire:

Enregistrer un commentaire