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 :

Classe abstraite et opérateur


Sujet :

C++

  1. #1
    Candidat au Club
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    2
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2007
    Messages : 2
    Par défaut Classe abstraite et opérateur
    Bonjour,

    Je cherche à créer une classe abstraite pour me servir d'interface, toutes les méthodes sont virtuelles pures. Plusieurs classes hériteront de cette interface.
    Seulement j'ai un soucis avec les opérateurs.

    Exemple de code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
     
    class A
    {
    public:
        virtual A& operator=(const A&) = 0;
    };
     
    class B: A
    {
    public:
        B& operator=(const B&)
        {
            return (*this);
        }
    };
     
    int main()
    {
        B b1, b2;
        b2 = b1;
     
        return 0;
    }
    J'ai l'erreur C2259 'B' impossible d'instancier une classe abstraite à cause du membre: A operator=(const A&) = 0: est abstrait (1 fois en ligne 10 et 2 fois en ligne 18).
    Mon opérateur de la classe B ne se substitue pas à celui de la classe A, qui, puisqu'il est abstrait, n'est pas défini, d'où l'erreur de compilation.

    Mais pourquoi il ne se substitue pas, et comment y parvenir ?

    J'imagine que ce doit être une erreur pas bien compliquée mais depuis ce matin j'ai le nez dedans et je focalise peut être un peu trop pour trouver la solution à mon pb

    Merci pour votre aide.

    IDE: VS2019 community v16.7.4.

    [EDIT] Correction d'une erreur de typo, oublié deux & dans l'exemple de code

  2. #2
    Candidat au Club
    Profil pro
    Inscrit en
    Mai 2007
    Messages
    2
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2007
    Messages : 2
    Par défaut
    Magique ce forum, il suffit de poster pour trouver

    Je sais pas pourquoi, je voulais assigner des objets au lieu de valeurs...

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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
     
    template <typename T> class A
    {
    public:
        virtual T operator=(T value) = 0;
    };
     
    class B: A<int>
    {
        int _value = 0;
    public:
        int operator=(int value) override
        {
            _value = value;
            return (_value);
        }
    };
     
    int main()
    {
        B b1, b2;
     
        b2 = 2;
     
        b2 = b1;
     
        return 0;
    }
    C'est mieux comme ca
    Merci !

  3. #3
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 635
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 635
    Par défaut
    Salut,

    De manière générale, les classes qui interviennent dans une hiérarchies de classes, et donc les classes abstraites servant d'interface, auront ce que l'on appelle une sémantique d'entité.

    En gros, ce sont des classes pour lesquelles nous ne voulons surtout pas qu'il puisse exister deux instances différentes de la classe présentant exactement les même valeurs internes.

    La raison est bien simple: il s'agira de classes comme Vehicule (dérivée en Camion, Voiture et Moto), Bâtiment (dérivée en Maison, Usine et Hangars), CompteEnBanque (dérivée CompteCourant, LivretDepargne ou CompteDinvestissement) ou Personnage (dérivée en Guerrier, Magicien ou Voleur). Bref toutes des classes pour lesquelles nous devons pouvoir identifier n'importe quelle instance de manière strictement unique afin de nous assurer que nous manipulons la bonne instance.

    Ce serait en effet ballot si, parce que nos comptes en banques étaient identifiés par les mêmes numéros, ton salaire finissait sur mon compte et que toutes mes factures étaient payées à partir du tien, non

    Ce sont donc des classes pour lesquelles la forme canonique orthodoxe de Coplien -- et surtout le constructeur de copie et l'opérateur d'affectation -- n'ont absolument aucun sens.

    Cela fait plus de vingt ans que nous nous sommes rendus compte de ce problème, et, à l'époque, la solution consistait donc à déclarer (sans les implémenter !!! ) le constructeur de copie et l'opérateur d'affectation dans l'accessibilité privée de la classe, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Base{
    public:
        /* on doit pouvoir construire et détruire une instance de la classe de base */
     
        Base(/* paramètres éventuels */); // implémentation dans le fichier .cpp correspondant
        /* on veut aussi pouvoir détruire n'importe quelle instance d'une classe 
         * dérivée "passant pour être" du type de base
         */
        virtual ~Base();// implémentation dans le fichier .cpp correspondant
        /* ... */
    private:
        /* par contre, on veut interdire l'appel au constructeur de copie et
         * à l'opérateur d'affectation
         * on les déclare dans l'accessibilité privée, sans fournir d'implémentation
         * pour eux (occasionnant une erreur d'édition de liens dans les cas les
         * plus complexes)
         */
        Base(Base const &); // constructeur de copie
        Base & operator = (Base const &); // opérateur d'affectation.
    };
    Mais les choses ont bien changé depuis, entre autres, depuis C++11 (qui a été mis en place en ... 2011), parce que l'on peut désormais déclarer ces deux fonctions comme étant delete, ce qui force le compilateur à considérer qu'ils n'existent pas, sous une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Base{
    public:
        /* le constructeur de la classe */
        Base(/* paramètres */); // implémentation dans le *.cpp correspondant
        Base(Base const &) = delete; // explicite le fait qu'il n'existe pas
        Base& operator = (Base const &) = delete; //idem
        virtual ~Base(); // implémentation dans le *.cpp correspondant
        /* ... */
    };
    En outre, il y a -- effectivement -- moyen d'utiliser une classe template comme classe de base (ou comme "interface"), de la manière dont tu t'y es pris.

    Cependant, il faut comprendre que ce genre de classes réagissent de manière totalement différente, si bien qu'il faut les utiliser de manière différente.

    Et, pour cela, je vais devoir commencer par t'expliquer les bases de la programmation générique.

    Pour "faire simple" (enfin, "pas trop compliqué", devrais-je dire ), il faut savoir que le principe des classes (et des fonctions) template est, en gros, de dire quelque chose comme
    Je ne sais pas encore quel sera le type réel de la donnée que je vais manipuler, c'est la raison pour laquelle je vais l'appeler typename TPar contre, je sais exactement la manière dont je vais la manipuler. D'ailleurs, voici la logique que je vais suivre:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template <typename T>
    class MaClasse{
    public:
     
        /* j'ai besoin d'une fonction qui fait <ceci> */
        voiid foo() {
            /* ce qui doit être fait en manipulant data */
        }
    private:
        T data;
    };
    Le principe étant que le compilateur va attendre de savoir ... par quel type il devra remplacer le type connu sous le "doux nom" de typename T pour pouvoir générer le code binaire exécutable correspondant à la classe (et à sa / ses fonctions).

    Cela implique que, du point de vue du compilateur, si tu as deux instances différentes de MaClasse, par exemple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    MaClasse<int> objInt;
    MaClasse<double> objDbl;
    le compilateur va considérer qu'il s'agit de deux types totalement différents, vu que l'une sera de type MaClasse<int> et que l'autre sera de type MaClasse<double>.

    Les deux instances présenteront certes la même fonction (foo, dans le cas présent) mais ne seront pas substituable l'une à l'autre, de la manière que le sous-entend l'héritage public "classique".

    Mais le plus important, c'est surtout de comprendre que tout ce travail de sélection du type adéquat s'effectue à la compilation, alors que le principe même des fonctions virtuelles est de sélectionner le type adéquat lors de l'exécution.

    Autant dire que la notion de "fonctions virtuelles" ne s'accordent pas vraiment avec la notion de "fonction membre de classe template", car elles ne "jouent pas dans la même cours"...

    Pire encore: lorsqu'il s'agit de fonctions virtuelles pures déclarées au niveau d'une classe template, il arrivera très rapidement un moment où le compilateur va gentiment t'envoyer péter lorsque tu essayera de créer une instance de la classe dérivée (qui définit pourtant le comportement de la fonction virtuelle pure) sous prétexte que "il reste (au moins) une fonction virtuelle pure, ce qui rend ta classe dérivée abstraite".

    Donc, si on doit résumer un peu les choses, on peut dire que:
    1. Si tu fais jouer l'héritage, tu ne veux sans doute ni de l'opérateur d'affectation ni du constructeur de copie
    2. si tu utilises une classe template comme classe de base, tu ne veux pas qu'elle expose une fonction virtuelle, et encore moins virtuelle pure
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

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

Discussions similaires

  1. Réponses: 3
    Dernier message: 09/11/2012, 10h59
  2. [Débutant(e)] toString + classes abstraites
    Par debdev dans le forum Langage
    Réponses: 9
    Dernier message: 26/01/2005, 15h22
  3. [Debutant][Conception] Classes abstraites et interface.
    Par SirDarken dans le forum Langage
    Réponses: 4
    Dernier message: 29/10/2004, 00h02
  4. Classe abstraite / MVC
    Par caramel dans le forum MVC
    Réponses: 5
    Dernier message: 01/04/2003, 09h27
  5. pb constructeurs classes dérivant classe abstraite
    Par Cornell dans le forum Langage
    Réponses: 2
    Dernier message: 10/02/2003, 19h02

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