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

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

 C++ Discussion :

surcharge d'opérateur et polymorphisme


Sujet :

C++

  1. #1
    Membre averti
    Profil pro
    Étudiant
    Inscrit en
    Avril 2006
    Messages
    26
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2006
    Messages : 26
    Par défaut surcharge d'opérateur et polymorphisme
    Bonjour,

    Je viens du monde Java et C. J'ai tout naturellement eu l'idée d'en "fusionner" la puissance via le C++.

    Je me heurte à un petit soucis avec la surcharge d'opérateur et le polymorphisme.

    Imaginons le code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    class A{
       public :
          virtual int& operator() (int x, int y) = 0;
    };
     
    class B : public A{
       private :
          int maVal;
       public :
          int& operator() (int x, int y){ return &maVal; }
    };
    L'idée étant de spécifier, via la classe abstraite A que les classes l'implémentant doivent proposer l'opérateur () pour l'accès à leur données. Et donc, puissent être utilisées ainsi, quelque soit l'implémentation :
    Le problème étant que le code suivant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    ...
    A monObj = B();
    monObj(2, 1) = 3;
    ...
    ne compile pas, pour la simple raison que A étant virtuelle, elle ne peut être instanciée.
    Pour utiliser le polymorphisme, on doit ainsi utiliser les pointeurs :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    ...
    A *monObj = new B();
    //et donc, de fait :
    (*monObj)(2,1) = 3;
    ...
    L'idée étant de mettre en place un découplage de l'interface et l'implémentation (pour faciliter la migration vers une autre librairie par exemple) mais aussi d'embellir le code des couches supérieures via la surcharge d'opérateur.

    Y'a-t-il un moyen de concilier les deux, au moins en masquant le déréférencement du pointeur ?

    Merci d'avance.

  2. #2
    Membre averti
    Profil pro
    Étudiant
    Inscrit en
    Avril 2006
    Messages
    26
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2006
    Messages : 26
    Par défaut
    Je pourrais évidemment créer une classe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    class ptrA{
       private :
          A* ptr;
       public :
          ptrA(A* pa):ptr(pa){}
          ~ptrA(){delete ptr;}
          int& operator() (int x, int y){ return (*ptr)(x,y);}
    };
    On aurait alors :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    ptrA monObj(new B());
    monObj(2,1) = 3;
    Mais ça devient un peu bordélique...

  3. #3
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut
    Bonjour,

    permière remarque:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    int& operator() (int x, int y){ return &maVal; }
    est incorrect (ça ne devrait pas compiler). Il faut faire comme suit:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    int& operator() (int x, int y){ return maVal; }
    En effet, en c++, le référencement est caché. C'est la même chose lorsqu'on utilise une variable passée par référence en paramètre d'une fonction. Par exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    // cette fonction:
    void UneFonction( const std::string & text );
     
    //va être utilisée ainsi:
    std::string monTexte;
    UneFonction( monTexte ); // référencement caché
    Pour le reste, je vais regarder ça de plus plus près et je reviens

  4. #4
    Membre averti
    Profil pro
    Étudiant
    Inscrit en
    Avril 2006
    Messages
    26
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2006
    Messages : 26
    Par défaut
    Oui, au temps (ou autant, c'est un long débat !) pour moi pour le référencement

  5. #5
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut
    Je n'arrive pas bien à cerner ton problème.
    Dans ton premier post tu proposes le code suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    A monObj = B();
    monObj(2, 1) = 3;
    qui ne fonctionne pas puisque A est abstraite.
    Pourquoi ne pas faire, tout simplement:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    B monObj;
    monObj(2, 1) = 3;
    ?

  6. #6
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Tu peux toujours utiliser une référence pour masquer le pointeur:
    Code C : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    A* pObj = new B();
    {
    	A& obj = *pObj;
    	obj(2, 1) = 3;
    }
    delete pObj;
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  7. #7
    r0d
    r0d est déconnecté
    Membre expérimenté

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    4 294
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur informatique

    Informations forums :
    Inscription : Août 2004
    Messages : 4 294
    Billets dans le blog
    2
    Par défaut
    Sinon, si c'est juste un problème de notation, tu peux utiliser une fonction membre plutôt qu'un opérateur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class A
    {
       virtual int& GetElement( int x, int y) = 0;
    };
     
    class B : public A
    {
       int& GetElement( int x, int y);
    };
    Ainsi, pour l'utiliser, ça donnera quelque chose comme ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    A* b = new B();
    b->GetElement(x,y);
    En fait, perso j'utilise très peu les opérateurs (et de moins en moins), car bien souvent ils ne permettent pas d'apporter la sématique nécessaire à un code limpide.

  8. #8
    Membre averti
    Profil pro
    Étudiant
    Inscrit en
    Avril 2006
    Messages
    26
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2006
    Messages : 26
    Par défaut
    Citation Envoyé par r0d Voir le message
    Je n'arrive pas bien à cerner ton problème.
    Dans ton premier post tu proposes le code suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    A monObj = B();
    monObj(2, 1) = 3;
    qui ne fonctionne pas puisque A est abstraite.
    Pourquoi ne pas faire, tout simplement:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    B monObj;
    monObj(2, 1) = 3;
    ?
    L'idée, c'est que l'implémentation de B n'étant pas tout à fait fixée (je risque de changer de librairie) j'utilise A pour en spécifier l'interface, via le polymorphisme. Ainsi, dans tout le reste de mon code, je manipule des A. Si je veux changer B (qui par exemple utilise la librairie CImg) pour C (qui utilise OpenCV) dérivant aussi de A, les modifications seront minimes avec ce procédé.

    Il est clair qu'utiliser une fonction comme tu le proposes est une possibilité, mais je préfèrerais (c'est plus joli) utiliser un opérateur surchargé.

    Si c'est la seule solution, je vais opter pour le couple A plus la classe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    class ptrA{
       private :
          A* ptr;
       public :
          ptrA(A* pa):ptr(pa){}
          ~ptrA(){delete ptr;}
          int& operator() (int x, int y){return (*ptr)(x,y);}
    };
    Afin d'avoir le comportement :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    ptrA monObj(new B());
    monObj(2,1) = 3;
    Comme je le décrivais précédemment. C'est assez lourd, y compris au niveau de la pile d'appels, mais plus élégant à l'utilisation.

    C'est vraiment dommage que le concept d'interface de Java n'existe pas en C++. Dès que j'aurais le temps, je regarderai si cela a été intégré au langage D.

  9. #9
    Membre averti
    Profil pro
    Étudiant
    Inscrit en
    Avril 2006
    Messages
    26
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2006
    Messages : 26
    Par défaut
    Voilà, c'est programmé.
    C'est joli, ça se comporte très bien.

    MAIS.

    Les performances prennent une certaine claque, comparément à du C, à algo strictement équivalent : le coût moyen en temps est doublé.

    En conclusion donc, ce fût un exercice de style intéressant, ravivant une énième fois cette éternelle question personnelle qui est : "comment concilier POO et performance ?"

    Merci en tous cas à tous pour votre disponibilité !

    [résolu]

  10. #10
    Expert éminent
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 395
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 395
    Par défaut
    Tu compiles bien en Release ?
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  11. #11
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 35
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    doublé par rapport à du C... j'aurais pas dit autant quand même.

  12. #12
    Membre averti
    Profil pro
    Étudiant
    Inscrit en
    Avril 2006
    Messages
    26
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2006
    Messages : 26
    Par défaut
    Oui, sous linux avec g++ sans aucune option de debug.

    Si tu veux un apperçu du code :

    - le test pour la version objet :
    http://pastebin.com/m19dabd23

    - le code de la classe abstraite :
    http://pastebin.com/m6c823e39

    - le code de son implémentation :
    http://pastebin.com/m42b3486b

    - la version sans tout le fratras objet :
    http://pastebin.com/md0813b5

    Si tu te demandes pourquoi j'ai ré-encapsulé CImg, c'est simplement parceque je ne suis pas certain de continuer à l'utiliser (me baser directement sur OpenGL et/ou la XLib me tente bien). Ca évitera de toucher aux autres classes qui doivent interragir avec des images (typiquement pour calculer Fft et transformée en curvelets).

  13. #13
    Membre Expert
    Avatar de Goten
    Profil pro
    Inscrit en
    Juillet 2008
    Messages
    1 580
    Détails du profil
    Informations personnelles :
    Âge : 35
    Localisation : France

    Informations forums :
    Inscription : Juillet 2008
    Messages : 1 580
    Par défaut
    Test avec -O3 pour voir si tu grapilles pas quelques précieuses secondes.

  14. #14
    Membre averti
    Profil pro
    Étudiant
    Inscrit en
    Avril 2006
    Messages
    26
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : France, Charente Maritime (Poitou Charente)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2006
    Messages : 26
    Par défaut
    Wow. Enorme gain, vraiment !

    Cela dit, énorme gain aussi pour l'autre version, si bien que le ratio reste inchangé. Oui, je chipote, mais la transformée en curvelets prends, selon l'article que j'ai sous les yeux, 94s pour une vidéo de 1k x 1k x 1k en répartissant le calcul sur 128 processeurs ... Alors j'ai tout intérêt à optimiser au maximum mes opérations simples ^^

  15. #15
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Citation Envoyé par Kyrel Voir le message
    Voilà, c'est programmé.
    C'est joli, ça se comporte très bien.

    MAIS.

    Les performances prennent une certaine claque, comparément à du C, à algo strictement équivalent : le coût moyen en temps est doublé.
    La formulation est un peu trompeuse. J'ai eu l'impression que c'était le wrapper de rien du tout présenté plus haut pour éviter d'avoir à écrire (*monObj)(2,1) = 3; qui avait conduit à doubler le temps de calcul et ça commençait à m'inquiétait un tantinet.

    Alors qu'en fait pas du tout.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    //IImg est un wrapper autour de _IImg pour l'accès direct à l'opérateur ()
    IImg<int> plop(CImg_Img<int>::create("lena.jpg"));
    clock_t ck = clock();
    for(int k=0; k<100; k++)
       for(unsigned int i=0; i<plop.width(); i++)
          for(unsigned int j=0; j<plop.height(); j++)
             plop(i,j,RED) = plop(i,j,GREEN) = 0;
    ck = clock()-ck;
    cout << ck << endl;
    et
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    _IImg<int>* plip = CImg_Img<int>::create("lena.jpg");
    _IImg<int>& plop = *plip;
     
    clock_t ck = clock();
    for(int k=0; k<100; k++)
       for(unsigned int i=0; i<plop.width(); i++)
          for(unsigned int j=0; j<plop.height(); j++)
             plop(i,j,RED) = plop(i,j,GREEN) = 0;
    ck = clock()-ck;
    cout << ck << endl;
    donne le même temps.

    Par contre le temps de calcul est effectivement doublé par rapport à l'utilisation directe de la classe image fourni par CImg. Donc au final le problème vient du fait que l'operateur() est virtuel, donc du polymorphisme...

  16. #16
    Expert confirmé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Décembre 2003
    Messages
    3 549
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Décembre 2003
    Messages : 3 549
    Par défaut
    Je viens du monde Java et C. J'ai tout naturellement eu l'idée d'en "fusionner" la puissance via le C++.
    Programmer à la C ou à la Java sont les deux pires manières de programmer en C++.

    Pourquoi ne pas plutôt faire un objet qui peut contenir tout type qui satisfait le concept que tu décris ?
    Ça s'appelle la type erasure, et ça permet d'ajouter ce genre de fonctionnalité de manière non intrusive, exception-safe, avec des sémantiques valeurs, et avec un contrôle sur l'allocation te permettant d'être plus efficace.
    Ça permet aussi d'étendre naturellement les mécanismes des templates pour permettre l'effacement du type manipulé en question, permettant plus de dynamisme.
    Certaines personnes recommandent de ne programmer que comme ça.

    Un exemple est par exemple boost::function<sig> (où sig est un type de fonction définissant une signature), qui est un type qui peut contenir un objet de n'importe quel type satisfaisant le concept Callable<sig>, c'est à dire qu'on peut l'appeler comme une fonction avec une certaine signature.
    En fait, ici, boost::function<int& (int, int)> fait exactement ce que tu veux...

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. polymorphisme et surcharge des opérateurs
    Par j.david dans le forum Langages de programmation
    Réponses: 1
    Dernier message: 02/01/2011, 19h17
  2. [C#] Tri d'objet et surcharge d'opérateur
    Par Royd938 dans le forum Windows Forms
    Réponses: 6
    Dernier message: 17/12/2007, 00h26
  3. Problème de surcharge d'opérateurs
    Par Hell dans le forum C++
    Réponses: 17
    Dernier message: 17/01/2005, 16h01
  4. Cumul de surcharges d'opérateurs
    Par Nats dans le forum C++
    Réponses: 2
    Dernier message: 11/10/2004, 13h37
  5. [VB .NET] Surcharge d'opérateur
    Par Franckintosh dans le forum VB.NET
    Réponses: 2
    Dernier message: 07/09/2004, 19h05

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo