Appel en boucle d'un constructeur

L'ECM est vraiment complet et minimal!

L’auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour,

J’ai un comportement que je n’arrive pas à expliquer. Par chance, j’ai réussi à réaliser un ECM qui met en évidence ce problème en un minimum de lignes.

Je souhaite avoir une classe et deux constructeurs: l’un sans parametre (ie. void), qui va appeler le second en passant un paramètre par défaut1. Dans mon ECM, la classe Foo se comporte parfaitement comme attendue, mais Bar appelle en boucle le premier constructeur. La seule différence : le type du paramètre. Voyez le code :

 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
#include <unistd.h>
#include <stdio.h>

#define PRINT(msg) do {printf("%s", msg); usleep(200*1000);} while(0);

class Foo {
public:
    Foo(void) {
        PRINT("Foo 1 pre\n");
        Foo(1);
        PRINT("Foo 1 post\n");
    }

    Foo(int i) {
        PRINT("Foo 2\n");
    }
};

class Bar {
public:
    typedef enum _ {
        EA,
        EB,
    } myenum;

    Bar(void) {
        PRINT("Bar 1 pre\n");
        Bar(EA);
        PRINT("Bar 1 post\n");
    }

    Bar(myenum i) {
        PRINT("Bar 2\n");
    }
};

int main(void)
{
    Foo f;
    Bar b;

    return 0;
}
1
2
3
4
5
6
7
8
9
$ ./a.out 
Foo 1 pre
Foo 2
Foo 1 post
Bar 1 pre
Bar 1 pre
Bar 1 pre
Bar 1 pre
...

Une explication ?

Merci d’avance pour vos réponses.


  1. hum, je devrait peut-être utiliser un seul constructeur et mettre une valeur par défault directement dans le prototype … 

Édité par Nodraak

Je n’ai pas regardé, en détail pourquoi ça ne marche pas pour Bar, je ne peux pas d’ici (je n’ai pas de compilo).

La différence que je vois est que tu utilises une enum, j’avoue que je ne me souviens plus comment sont gérés le typage des enum en C++.

Mais par contre, le comportement que tu essaye de faire, cela correspond aux valeurs par défaut, pourquoi ne pas tout simplement faire :

1
2
3
4
5
    Foo(int i=1) {

    }
    Bar(myenum i=EA) {
    }

EDIT : ah, bah je viens de voir ta footnote, du coup, tu avais déjà remarqué :p

hum, je devrait peut-être utiliser un seul constructeur et mettre une valeur par défault directement dans le prototype …

Édité par satenske

+0 -0

Je ne suis pas un expert du C++. Et ce cas est très bizarre.

Edit : Explication foireuse …

Édité par ache

ache.one                 🦹         👾                                🦊

+0 -0

Cette réponse a aidé l’auteur du sujet

Lu’!

Je souhaite avoir une classe et deux constructeurs: l’un sans parametre (ie. void), qui va appeler le second en passant un paramètre par défaut.

Nodraak
  1. Pour ta footnote, oui, très clairement.
  2. Ton actuel ne fait pas ça, il crée un objet temporaire dans le constructeur qui est détruit immédiatement. Utilise la liste d’initialisation, c’est son boulot (et ça corrige l’exécution en boucle).

Par contre, expliquer le problème pour l’instant je sèche, même si je regarde.

First : Always RTFM - "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein [Tutoriel Frama-C WP]

+0 -0

Pour une raison que j’ignore, la construction du temporaire avec Bar{EA} au lieu de Bar(EA) produit le comportement voulu.

Après, de la à savoir pourquoi gcc (et clang, j’ai testé) décident d’appeler le constructeur par défaut avec le code d’origine, mystère.

+1 -0

Cette réponse a aidé l’auteur du sujet

Un autre indice, le compilateur prend l’instruction pour une … déclaration :

1
2
3
4
5
6
7
8
9
struct Bar {
    enum e { A, B };
    Bar(){ Bar(A); }
};

int main(void){
    Bar b;
    return 0;
}

compile ^^ .

Et VS nous dit :

c:...\main.cpp(3): warning C4717: ’Bar::Bar’ : récurrent sur tous les chemins d’accès de contrôle, la fonction entraînera un dépassement de capacité de la pile d’exécution.

A noter, comme c’est un UB :

  • GCC segfault
  • Clang optimise en virant l’appel
  • VS stack overflow.

EDIT :

Merci @REMqb pour l’info, c’est dû au parsing. Le compilateur croit que A, est le nom de l’objet que l’on déclare de type Bar. D’où l’appel récursif infini. Moralité, n’utilisez ces f*cking parenthèses pour initialiser que quand vous n’avez pas d’autre choix et utilisez la liste d’initialisation.

https://stackoverflow.com/questions/27176956/error-constructing-temporary-object-whose-constructor-takes-a-single-enum-parame

Édité par Ksass`Peuk

First : Always RTFM - "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein [Tutoriel Frama-C WP]

+1 -0

Ca m’étonne que le compilateur ne sorte pas un warning disant qu’on redéclare EA avec un type différent …

Nodraak

tu ne redéclares pas EA, tu crées un objet temporaire sans nom. Dans ce contexte-ci, Bar(EA); est identique à Bar variable(EA); (sans avoir le warning "variable non utilisée")

+1 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte