83 votes

Initialiser des variables membres en utilisant le même nom pour les arguments des constructeurs que pour les variables membres autorisées par la norme C++ ?

J'ai découvert qu'il est possible d'initialiser les variables membres avec un argument de constructeur du même nom comme le montre l'exemple ci-dessous.

#include <cstdio>
#include <vector>

class Blah {
    std::vector<int> vec;

public:
    Blah(std::vector<int> vec): vec(vec)
    {}

    void printVec() {

        for(unsigned int i=0; i<vec.size(); i++)
            printf("%i ", vec.at(i));

        printf("\n");
    }
};

int main() {

    std::vector<int> myVector(3);

    myVector.at(0) = 1;
    myVector.at(1) = 2;
    myVector.at(2) = 3;

    Blah blah(myVector);

    blah.printVec();

    return 0;
}

g++ 4.4 avec les arguments -Wall -Wextra -pedantic ne donne aucun avertissement et fonctionne correctement. Il fonctionne également avec clang++. Je me demande ce que la norme C++ dit à ce sujet ? Est-il légal et garanti de toujours fonctionner ?

100voto

Nawaz Points 148870

Je me demande ce que la norme C++ dit à ce sujet ? Est-il légal et garanti de toujours fonctionner ?

Oui. C'est parfaitement légal. Entièrement conforme aux normes.

Blah(std::vector<int> vec): vec(vec){}
                             ^   ^                           
                             |   |
                             |    this is the argument to the constructor
                             this is your member data

Puisque vous avez demandé la référence dans la norme, la voici, avec un exemple.

§12.6.2/7

Les noms dans la liste d'expression d'un initialisateur de mem sont évalués dans la portée du constructeur pour lequel l'initialisateur de mem est spécifié.

[Example:
class X {
 int a;
 int b;
 int i;
 int j;
 public:
 const int& r;
  X(int i): r(a), b(i), i(i), j(this->i) {}
                      //^^^^ note this (added by Nawaz)
};

initialise X::r pour faire référence à X::a, initialise X::b avec la valeur du paramètre i du constructeur. paramètre i du constructeur, initialise X::i avec la valeur du paramètre i du constructeur. constructeur i, et initialise X::j avec la valeur de la valeur de X::i ; ceci a lieu chaque fois qu'un objet de la classe X est créé. ]

[Note : parce que les mem-initializer sont évalués dans le du constructeur, le pointeur this peut être utilisé dans la liste d'expression d'un mem-initializer pour faire référence à l'objet qui est initialisé. ]

Comme vous pouvez le constater, il y a d'autres éléments intéressants à noter dans l'exemple ci-dessus, ainsi que dans le commentaire de la norme elle-même.


En passant, pourquoi ne pas accepter le paramètre en tant que const référence :

 Blah(const std::vector<int> & vec): vec(vec) {}
      ^^^^const              ^reference

Il évite la copie inutile de l'objet vectoriel original.

15voto

Son fonctionnement est toujours garanti (je l'utilise assez souvent). Le compilateur sait que la liste des initialisateurs est de la forme : member(value) et il sait donc que le premier vec en vec(vec) doit être un membre. Maintenant, sur l'argument pour initialiser le membre, les deux membres, les arguments au constructeur et d'autres symboles peuvent être utilisés, comme dans toute expression qui serait présente à l'intérieur du constructeur. A ce stade, il applique les règles de recherche régulière, et l'argument vec cache le membre vec .

La section 12.6.2 de la norme traite de l'initialisation et explique le processus, le paragraphe 2 traitant de la recherche du membre et le paragraphe 7 de la recherche de l'argument.

Les noms dans la liste d'expressions d'un initialisateur de mémoire sont évalués dans la portée du constructeur pour lequel l'initialisateur de mémoire est spécifié. [Exemple :

class X {
   int a;
   int b;
   int i;
   int j;
public:
   const int& r;
   X(int i): r(a), b(i), i(i), j(this->i) {}
};

2voto

Artalizian Points 76

Un contre-argument supplémentaire, ou peut-être simplement quelque chose dont il faut être conscient, est la situation dans laquelle la construction move est utilisée pour initialiser la variable membre.

Si la variable membre doit être utilisée dans le corps du constructeur, alors la variable membre doit être explicitement référencée par le biais de ce pointeur, sinon la variable déplacée sera utilisée, ce qui est dans un état non défini.

template<typename B>
class A {
public:

  A(B&& b): b(std::forward(b)) {
    this->b.test(); // Correct
    b.test(); // Undefined behavior
  }

private:
  B b;
};

1voto

user1387866 Points 2165

Comme d'autres ont déjà répondu : Oui, c'est légal. Et oui, le fonctionnement est garanti par la norme.

Et je trouve ça horrible à chaque fois que je le vois, ce qui me force à faire une pause : " vec(vec) ? WTF ? Ah oui, vec est une variable membre..."

C'est l'une des raisons pour lesquelles beaucoup, dont moi, aiment utiliser une convention de dénomination qui indique clairement qu'une variable membre est une variable membre. Les conventions que j'ai vues incluent l'ajout d'un suffixe souligné ( vec_ ) ou un m_ préfixe ( m_vec ). Ensuite, l'initialisateur se lit : vec_(vec) / m_vec(vec) ce qui est une évidence.

-1voto

johnathon Points 1593

Les constructeurs surchargés peuvent être instanciés pour définir les données des membres. Comme avec n'importe quel constructeur, de n'importe quelle classe, vous pouvez utiliser une liste séparée par des virgules pour initialiser les variables membres de cette classe, ainsi que des classes héritées.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X