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 :

Ambiguïté sur le surcharge des fonctions


Sujet :

C++

  1. #1
    Membre du Club
    Homme Profil pro
    12
    Inscrit en
    Mai 2014
    Messages
    67
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 34
    Localisation : France, Loire Atlantique (Pays de la Loire)

    Informations professionnelles :
    Activité : 12
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2014
    Messages : 67
    Points : 61
    Points
    61
    Par défaut Ambiguïté sur le surcharge des fonctions
    bonsoir,

    je dispose de 3 versions d'une fonction F :

    int F( A a, int i ) { cout<<"la fonction : A a, int i "<< endl ; }
    int F( A a, double d ) { cout<<"la fonction : A a, double d "<< endl ; }
    double ( B b , int i ) { cout<<"la fonction : B b, int i "<< endl ; }


    dans le main je declare dans mon main les instructions suivantes :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    A a; B b
    int i;float l;double d;char c; 
     
    i=f(a,i);
    l=f(a,i);
    i=f(b,i);
    d=f(b,i);
    d=f(b,l);
    d=f(b,c);
    i=f(i,l);
    normalement le compilateur devra choisir la fonction par les types d'arguments, ca c'est claire mais pourquoi il ne genère par d'erreure lorsque le type de retourne n'est le meme que celui a qui il est affecter dans le main !!?

    Exemple de test :


    qui peut m'expliquer svp comment ca marche ?
    Images attachées Images attachées  

  2. #2
    Expert éminent sénior

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 189
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 189
    Points : 17 141
    Points
    17 141
    Par défaut
    C'est parce qu'il existe des conversions implicites, et que convertir un type vers un type "plus grand" est toujours possible et sans warning.

    Un extrait de la chaine de conversion:
    short -> int -> long -> float -> double.

    La conversion entre entiers s'appelle promotion entière.
    Par ailleurs, j'imagine que ne te pose pas de problème. Pourtant, 2 est une constante de type int.


    Considérons à présent les fonctions sur les nombres suivantes:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int f(int);
    int f(double);
    double f(double);//impossible, il existe déjà une f(double);
     
    int f(int& a, int const& b);
    voici quelques résultats possibles:
    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
    int un = 1, deux = 2;
    int & un_aussi = un;//référence modifiante sur un.
    int const& deux_aussi = deux; //référence constante sur deux
    double bidule = 1.5d;
     
    un = f(un);
    deux = f(bidule);
    //via les références, un_aussi et deux_aussi ont été modifié.
    un_aussi = f(un);
    deux_aussi = f(deux);//ne compile pas, deux_aussi est une (référence) constante
     
    f(un, deux); //les paramètres a et b sont deux références initialisés sur un et deux
    f(un_aussi, deux_aussi);// références sur références, c'est légitime
     
    f(un, 2); // on peut donner une valeur temporaire à un paramètre en référence constante...
    f(1, deux); // impossible car : ... mais pas pour une référence modifiante.

    Il y a d'autres cas possible.
    Les références et pointeurs, par exemple, peuvent présenter des subtilités.
    Avec de l'héritage, convertir une classe fille à une de ses classes de base est valable, de même que la conversion via un constructeur non explicit ou les opérateurs de conversions.

    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
    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
    class A {};
    class B: public A{};
    class C: public A{};
     
    class D: private A{}; //ou bien class D : A {};
     
    class E {
    public:
        E();
        E(B const&);
        explicit E(A const&);
     
        operator int () const;
        operator B() const;
    };
     
    int main() {
    	A a;
    	B b;
    	B const constante = b; //on peut initialiser une constante avec une variable car c'est une copie
    	B autre = constante; //on peut initialiser une variable avec une constante car c'est une copie
     
    	constante = autre; //impossible, on affecte une constante
    	autre = constante; // affecter une constante dans une variable est normal.
     
    	C c;
    	D d;
     
    	//conversion implicite en classe de base
    	a = b;
    	a = constante; //conversion d'un B const en A.
     
    	a = c;// de même
     
    	a = d;//ne compile pas car D n'est pas un A. (l'héritage privé n'est pas visible en dehors de la classe dérivée)
     
     
    	//les références:
     
    	A& a1 = a; //normal
    	A& a2 = b; //B est un A
    	A& a3 = c; // C aussi
     
    	A& a4 = d; // ne compile pas, car Casse n'est pas un A
     
    	B & b1 = b;
    	B const& b_const = b; //une référence constante, est toujours possible.
     
    	B & b2 = constante; // impossible, on ne peut pas prendre une référence modifiante sur une constante.
     
    	//constructions
    	E e1(a);
    	E e2(b);
    	E e3(c); //fonctionne parce que C est publiquement un A, et que c'est une construction explicite.
    	E e3 = c; //fonctionne aussi, parce qu'il s'agit d'une construction explicite. (normalement)
     
    	//affectations: usage de operator=(E const&) défini par défaut.
    	//le compilateur cherche à créer un E const& à partir de la valeur affectée
    	E e; //possible grâce au constructeur par défaut
     
    	e = b; // un E temporaire est construit via le constructeur E(B const&) car il n'est pas explicite.
    	e = a; // ne compile pas, car E(A) est explicite.
    }

    Considérons à présent les fonctions:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    int f1(A);
    int f2(B);
    int f3(A const& ref);
     
    int f4(E const& );
    alors on a:
    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
    A a;
    B b;//public a
    C c;//public a
    D d;//private a
     
    f1(a);//copie de l'argument
    f1(b); f1(c);//copie par création d'une temporaire de type A. phénomène de slicing
     
    f1(d); // ne compile toujours pas, d n'est pas un A
     
    f2(b); //légitime.
    f2(a); //impossible a n'est pas un B.
     
    f3(b); //la, ca devient intéressant le paramètre ref de f3, une A const& est initialisée sur b. Il n'y a pas de slicing.
     
    f4(a);//échec: tentative de construction d'un E temporaire, pris en référence constante, mais le constructeur est explicite.
    f4(b);//possible, il y a bien un constructeur, implicite.
    f4(c);//impossible, il y a bien un constructeur prenant un A, mais il est explicite.
    f4(E(c)); //valide, construction explicite.
    f4(d);//impossible, comme toujours, parce que d n'est pas pas un B (seul constructeur explicite).
    ajoutons encore une classe;
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    class tricheur{
        operator E () const;//operateur de conversion (mais sans sa définition)
    };
    alors on peut jouer dans l'autre sens:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    tricheur t;
    f4(t);//est possible, car t est implicitement converti en E.
    f4( tricheur() );//construction d'un temporaire de type tricheur, converti implicitement en une temporaire de E prise en référence constante.
    J'en viens à d'autres subtilités qu'il vaut mieux connaitre:
    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
    E const& f1(E const& e) {return e;}
    E const& f2(E & e) {return e;}
    E & f3(E & e) {return e;}
    E f4(E const& e) {return e;}
    E const & CAUSE_D_ERREUR() {return E();}
    E & GRAVE_ERREUR() {return E();}
     
    int main() {
    	E variable;
    	E const constante;
     
    	E e;
    	e = f1(variable); // valable. e est affectée avec variable (via deux références)
    	e = f1(constante); // valable. e est affectée avec constante (via deux références)
     
    	e = f2(variable); //de même.
    	e = f2(constante); //erreur, référence variable prise sur une constante
     
    	e = f3(variable); //valable.
    	e = f4(variable); // une copie de variable est retournée, et sers à affecter e.
     
    	e = CAUSE_D_ERREUR(); une référence constante sur une temporaire est retournée, c'est compilable, et le compilateur prolonge gentillement la durée de vie de la temporaire le temps de l'instruction.
    	E const& BOUM = CAUSE_D_ERREUR();// le compilateur fait de même, sauf que BOUM est une référence, sur une temporaire qui n'existe plus après le ; c'est une "dangling reference" = une SEGFAULT en perspective.
     
    	E & GRAVE_ERREUR() {return E();}// c'est encore pire, la gentillesse du compilateur n'étant pas appliquée avec les retours par référence modifiante.
    }
    La règle à retenir de ce dernier point, c'est de ne jamais retourner une référence sur une temporaire. C'est plus sûr.
    (comme toute règle, quand tu sauras comment ne pas te planter en la violant, tu verras)
    Mes principes de bases du codeur qui veut pouvoir dormir:
    • Une variable de moins est une source d'erreur en moins.
    • Un pointeur de moins est une montagne d'erreurs en moins.
    • Un copier-coller, ça doit se justifier... Deux, c'est un de trop.
    • jamais signifie "sauf si j'ai passé trois jours à prouver que je peux".
    • La plus sotte des questions est celle qu'on ne pose pas.
    Pour faire des graphes, essayez yEd.
    le ter nel est le titre porté par un de mes personnages de jeu de rôle

Discussions similaires

  1. Réponses: 2
    Dernier message: 29/10/2006, 18h52
  2. Réponses: 3
    Dernier message: 21/10/2006, 16h03
  3. pointeurs sur les arguments des fonctions?
    Par brunoP dans le forum C
    Réponses: 3
    Dernier message: 14/05/2006, 18h11
  4. [G++] Ambuguité sur une surcharge de fonction
    Par Beuss dans le forum Autres éditeurs
    Réponses: 3
    Dernier message: 14/05/2006, 13h51
  5. [MFC] Surcharger des fonctions de CView
    Par Philippe320 dans le forum MFC
    Réponses: 2
    Dernier message: 22/11/2005, 21h24

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