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 avec constructeurs multiples


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut Classe avec constructeurs multiples
    Bonjour à tous.

    J'essaie de faire une classe qui contient 2 constructeurs.
    Et je veux définir le second constructeur en faisant appel au premier comme suit :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class A {
    public:
    	A(std::vector<int> vec):_vec(vec){};
    	A(int x, int y){
    			std::vector<int> vec(2);
    			vec[0]=x; vec[1]=y;
    			A(vec);
    	};
    protected:
    	std::vector<int> _vec;
    };
    sauf que la ligne "A(vec)" produit l'erreur suivante :
    test.cxx:48:6: error: redefinition of 'vec' with a different type: 'A' vs 'std::vector<int>'
    A(vec);

    test.cxx:46:21: note: previous definition is here
    std::vector<int> vec(2);

    1 error generated.
    Quelqu'un peut-il m'éclairé ?

    Merci

  2. #2
    Expert confirmé
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 766
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 766
    Par défaut
    Déjà ton code est bizarre
    Pourquoi vouloir créer un temporaire pour initialiser son attribut en se cassant le trognon à appeler un autre constructeur?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    A(int x, int y): _vec(2, 0) {
        _vec[0] = x;
        _vec[1] = y;
    };
    Et ensuite il me semble que tu ne peux pas le faire, et il faut créer une méthode qui initialise ton vecteur et qui sera appelée dans les constructeurs

    Édit: Que c'est moche les tirets-bas dans les noms des attributs, et d'ailleurs il me semble que c'est déconseillé
    Moi, je suis extrémiste: je ne précède même pas le nom de mes attributs par "m_" (ni de n'importe quelle autre indication)

  3. #3
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    760
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 760
    Par défaut
    Depuis C++11 il est possible qu'un constructeur appel un autre constructeur (possible uniquement dans la liste d'initialisation)

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct S {
      S() :S(42) {}
     
      S(int i) : i(i) {}
     
      int i;
    };
     
    S().i == 42
    Mais il y a encore plus simple: fournir au vecteur les valeurs de départ.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    A(int x, int y): _vec({x,y}) {
    };

  4. #4
    Membre Expert Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

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

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Par défaut
    Citation Envoyé par foetus Voir le message
    Déjà ton code est bizarre
    Pourquoi vouloir créer un temporaire pour initialiser son attribut en se cassant le trognon à appeler un autre constructeur?
    Même si ici ce n'est pas très bien appliqué, c'est très commode comme technique .

  5. #5
    Membre Expert Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

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

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Par défaut
    Citation Envoyé par foetus Voir le message
    Oui c'est la technique C++11 pour manipuler les temporaires
    Mais a 2-3 subtilités cela revient à ce que j'ai dit : à créer une autre méthode (donc ici swap) et l'appeler des constructeurs et des opérateurs
    Pas du tout, cela existait bien avant C++11.

    Même si « Code < C++11 à l'arrache non testé », ton code est "archi faux".

    Citation Envoyé par foetus Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    protected:
     
        std::vector<int> vec;
    };
    Par convention (pas du tout obligatoire), j'utiliserais m_vector au lieu de vec.
    Pourquoi mettre cette donnée membre en protected ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    private:
        std::vector<int> m_vector;
    Et je mettrais le constructeur par défaut, qui a du sens (à partir de là tout est public) :
    C++11
    C++03
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A() : m_vector() { } // A() { } devrait suffire
    Citation Envoyé par foetus Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    A(std::vector<int> init_vec) {
            swap(init_vec);
        };
    La liste d'initialisation suffit amplement.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A(std::vector<int> const & vector) : m_vector(vector) { }
    Citation Envoyé par foetus Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    A(A& init_a) {
            std::vector<int> copy_vec(init_a.vec);
     
            swap(copy_vec);
        };
    Le constructeur de copie est généré automatiquement par le compilateur et fait la bonne chose, pas besoin de l'écrire ; mais si on devait le faire, ça serait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A(A const & a) : m_vector(a.m_vector) { }
    Là aussi, la liste d'initialisation suffit amplement, en plus si tu comptes utiliser l'idiome copy & swap, utiliser swap dans le constructeur par copie, ferait une boucle infinie (opérateur d'affectation qui appelle le constructeur par copie, qui appelle swap, qui appelle le constructeur par copie, ...)

    Citation Envoyé par foetus Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    A(int x, int y){
            std::vector<int> new_vec(2);
            new_vec[0] = x;
            new_vec[1] = y;
     
            swap(new_vec)
        };
    C++11
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A(int const x, int const y) : m_vector({ x, y }) { }
    C++03 (on pourrait faire une fonction qui prend deux entiers et retourne un vecteur avec ces deux entiers pour faire "comme en C++11")
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A(int const x, int const y) : m_vector(2) { m_vector[0] = x; m_vector[1] = y; }
    Citation Envoyé par foetus Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    // Parameter: neither pointer nor reference
        A& operator=(A other_a) {
            swap(other_a.vec);
     
            return *this;
        };
    L'opérateur d'affectation est généré automatiquement par le compilateur et fait la bonne chose, pas besoin de l'écrire ; mais si on devait le faire, ça serait :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A & operator =(A const & a) { m_vector = a.vector; return *this; }
    Si on a une classe un peu plus compliquée, on peut utiliser l'idiome copy & swap
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A & operator =(A const & a) { A tmp(a); std::swap(*this, tmp); return *this; }
    La fonction template std::swap de la bibliothèque standard fait la bonne chose, si ce n'est pas le cas, on la spécialise. Voici une proposition :
    On écrit la fonction membre.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    void swap(A & a)
    {
        using std::swap; // Pas nécessaire ici, mais c'est une bonne pratique (merci Flob90)
        swap(m_vector, a.m_vector);
    }
    Et voila la spécialisation (c'est une fonction libre).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    namespace std
    {
        template <>
        void swap(A & a, A & b) { a.swap(b); }
    }
    Et pour :
    Citation Envoyé par foetus Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    // Parameter: reference
        void swap(std::vector<int>& other_vec) {
            vec.swap(other_vec);
        };
    std::vector<int> n'a pas de fonction membre swap(). Voir ma proposition au dessus.

    En fait si -_- (merci foetus).

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 147
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 147
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par Ehonn Voir le message
    C++03
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A() : m_vector() { } // A() { } devrait suffire
    Ton constructeur est même totalement inutile : le constructeur par défaut des membres est déjà appelé implicitement.

    Citation Envoyé par Ehonn Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A & operator =(A const & a) { m_vector(a.vector); return *this; }
    T'as été un peu vite là, le std::vector n'a pas d'opérateur() qui prend en paramètres un autre vecteur.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    A & operator =(A const & a) { m_vector = a.vector; return *this; }
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  7. #7
    Membre Expert Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

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

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Par défaut
    Citation Envoyé par Bousk Voir le message
    Ton constructeur est même totalement inutile : le constructeur par défaut des membres est déjà appelé implicitement.
    Il est pas utilisé ici pour l'exemple mais c'est souvent pratique d'avoir un constructeur par défaut.
    Oui, le constructeur par défaut des données membre est appelé automatiquement mais pas celui des types de bases (ou alors, il ne fait pas la bonne chose). Autrement dit, int i; est différent de int i = int();. C'est pour cela que je préfère écrire explicitement l'initialisation par défaut des données membre (mais cela ne dépend que de moi).

    Citation Envoyé par Bousk Voir le message
    T'as été un peu vite là, le std::vector n'a pas d'opérateur() qui prend en paramètres un autre vecteur.
    Oups, désolé, copier-coller :s Je corrige, merci

  8. #8
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    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 202
    Par défaut
    Ce que vous dites est vrai, mais vous n'avez pas expliqué pourquoi c'est impossible d'appeler un autre constructeur comme lg_53 le faisait.

    La création d'une variable ou d'un pointeur se fait en deux étapes: allocation d'une zone de mémoire (sur la pile, ou sur le tas avec new), puis construction de la variable.

    Pour un type primitif, une enum ou une union la construction est simple: rien du tout, c'est à dire valeur arbitraire. (Undefined behaviour pour être précis)
    Pour un tableau, la règle est un peu plus subtile, selon ce qui est fourni (ou non) comme initialiseur et comme taille.
    Pour un type construit (struct, class), c'est le constructeur qui est appelé (constructeur par défaut s'il n'y a pas d'argument d'initialisation)

    Ce constructeur travaille sémantiquement: en deux temps, initialisation des classes parentes et variables membres, puis traitement global.
    L'initialisation est faite dans la liste d'initialisation (d'où le nom), et le traitement dans le corps du constructeur.

    On ne peut pas appeler un autre constructeur dans ce corps, parce que justement les variables ont déjà été initialisées (tout particulièrement les constantes et les références).

    C++11 ajoute la possibilité d'appeler un autre constructeur dans certains cas mais, pour la même raison, dans la liste d'initialisation.


    PS: il me semble que pour un constructeur, les types primitifs sont mis à 0.

  9. #9
    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
    La réponse est simple: La syntaxe la plus intuitive pour appeler un constructeur hors de la liste d'initialisation est déjà utilisée pour instancier un objet temporaire.

    Exemple:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    A::A(int a)
    {
    	m_a = a;
    	m_str = "def";
    }
     
    A::A(std::string str)
    {
    	A(42); //Cette ligne ne fait pas ce que tu crois!
    	m_str = str;
    }
    La ligne 42 n'appelle pas un autre constructeur, mais crée un nouvel objet temporaire, immédiatement détruit!



    Mais ce n'est pas le seul problème: Imaginons que tu réussisses à faire ce que tu veux avec un placement new
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    A::A(std::string str)
    {
    	new(this) A(42);
    	m_str = str;
    }
    Félicitations, tu viens d'écraser des données déjà initialisées avec d'autres. Aucun destructeur ni opérateur = n'a été appelé, vu que le constructeur compte sur le fait que les données ne soient pas encore initialisées: Félicitations, tu as fait l'équivalent d'un memcpy() en C++. Très mauvais.
    C'est pour ça qu'on ne peut appeler un constructeur du même objet que dans la liste d'initialisation. Ou par copy-and-swap.
    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.

  10. #10
    Membre Expert
    Profil pro
    Inscrit en
    Mars 2007
    Messages
    1 415
    Détails du profil
    Informations personnelles :
    Localisation : France, Paris (Île de France)

    Informations forums :
    Inscription : Mars 2007
    Messages : 1 415
    Par défaut
    Ehon, ce n'est pas fair play de chipoter sur les conventions de nommages (m_vector <-> vec).

    Citation Envoyé par leternel Voir le message
    PS: il me semble que pour un constructeur, les types primitifs sont mis à 0.
    Que veux-tu dire par là ? Les types primitifs ne sont mis à leur valeur par défaut (zéro la plupart du temps) qui si explicitement ajoutés dans un constructeur. Exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <iostream>
     
    struct Roger {
      int a;
      int b[10];
    };
     
    int main() {
      Roger r;
      std::cout << r.a << "\n";
      for(int i = 0; i < 10; ++i) std::cout << r.b[i];
      std::cout << std::endl;
      return 0;
    }
    Affiche une horreur du genre :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    1964710984
    326184197008000419652801178070192327670
    Mais

    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
    #include <iostream>
     
    struct Roger {
      int a;
      int b[10];
      Roger()  : a(), b() {}
    };
     
    int main() {
      Roger r;
      std::cout << r.a << "\n";
      for(int i = 0; i < 10; ++i) std::cout << r.b[i];
      std::cout << std::endl;
      return 0;
    }
    Affiche bien:

    A noter, la syntaxe avec des parenthèses y compris pour l'array, qui est entièrement initialisé à zéro. En C++11, on peut utiliser des accolades à la place des parenthèses.

  11. #11
    Expert confirmé
    Homme Profil pro
    Analyste/ Programmeur
    Inscrit en
    Juillet 2013
    Messages
    4 766
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Analyste/ Programmeur

    Informations forums :
    Inscription : Juillet 2013
    Messages : 4 766
    Par défaut
    Citation Envoyé par Ehonn Voir le message
    std::vector<int> n'a pas de fonction membre swap(). Voir ma proposition au dessus.
    Perdu

    std::vector::swap

  12. #12
    Membre Expert Avatar de Ehonn
    Homme Profil pro
    Étudiant
    Inscrit en
    Février 2012
    Messages
    788
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

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

    Informations forums :
    Inscription : Février 2012
    Messages : 788
    Par défaut
    Autant pour moi, je corrige ça

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

Discussions similaires

  1. tableau de classe avec constructeur non copiable
    Par pixelou dans le forum Débuter
    Réponses: 3
    Dernier message: 22/02/2010, 23h09
  2. Système de classe, avec héritage multiple dynamique
    Par kimjoa dans le forum Contribuez
    Réponses: 0
    Dernier message: 09/01/2010, 18h07
  3. class avec constructeur déclarer en static
    Par tims12 dans le forum Langage
    Réponses: 4
    Dernier message: 29/12/2009, 17h58
  4. Héritage d'une classe avec constructeur privé
    Par Braillane dans le forum Langage
    Réponses: 13
    Dernier message: 02/09/2009, 11h59
  5. Réponses: 1
    Dernier message: 28/03/2006, 22h08

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