Quand dois-je utiliser les listes d'initialisation pour initialiser les membres de la classe C ++?

StackOverflow https://stackoverflow.com/questions/1632484

  •  06-07-2019
  •  | 
  •  

Question

disons que j'ai     std::map< std::string, std::string > m_someMap en tant que variable membre privée de la classe A

Deux questions: (et la seule raison pour laquelle je pose la question est parce que je suis tombé sur un code comme ça)

  1. Quel est le but de cette ligne:

    A::A() : m_someMap()
    

    Maintenant, je sais que c’est l’initialisation, mais est-ce que vous devez faire cela comme ça? Je suis confus.

  2. Quelle est la valeur par défaut de <=>, C # définit également que int, double, etc. est toujours initialisé sur defualt 0 et que les objets sont nuls (du moins dans la plupart des cas) Alors, quelle est la règle en C ++? les objets sont-ils initialisés par defualt à null et les primitives à garbage? Bien sûr, je parle de variables d'instance.

EDIT:

aussi, puisque la plupart des gens ont fait remarquer qu'il s'agissait d'un choix de style et que cela n'était pas nécessaire, qu'en est-il:

A :: A (): m_someMap (), m_someint (0), m_somebool (false)

Était-ce utile?

La solution

m_somemap

  1. Vous n'êtes pas obligé.
  2. Ce que vous obtenez si vous l'omettez: Un std::map< std::string, std::string > vide, c'est-à-dire une instance valide de cette carte qui ne contient aucun élément.

m_somebool

  1. Vous devez l'initialiser sur true ou false si vous souhaitez que sa valeur soit connue. Les booléens sont & "Anciens types de données" & "; et ils n'ont pas la notion de constructeur. De plus, le langage C ++ ne spécifie pas les valeurs par défaut pour les booléens non initialisés explicitement.
  2. Ce que vous obtenez si vous l'omettez: un membre booléen avec une valeur non spécifiée. Vous ne devez pas faire cela et utiliser plus tard sa valeur. C'est pourquoi il est fortement recommandé d'initialiser toutes les valeurs de ce type.

m_someint

  1. Vous devez l'initialiser à une valeur entière si vous voulez qu'il ait une valeur connue. Les entiers sont & "Anciens types de données" & "; et ils n'ont pas la notion de constructeur. De plus, le langage C ++ ne spécifie pas les valeurs par défaut pour les entiers non explicitement initialisés.
  2. Ce que vous obtenez si vous l'omettez: un membre int avec une valeur non spécifiée. Vous ne devez pas faire cela et utiliser plus tard sa valeur. C'est pourquoi il est fortement recommandé d'initialiser toutes les valeurs de ce type.

Autres conseils

Il n’est pas nécessaire de le faire.
Le constructeur par défaut le fera automatiquement.

Mais parfois, en le rendant explicite, il agit comme une sorte de documentation:

class X
{
    std::map<string,string>  data;
    Y                        somePropertyOfdata;

    X()
      :data()                    // Technically not needed
      ,somePropertyOfdata(data)  // But it documents that data is finished construction
    {}                           // before it is used here.
};

La règle en C ++ est que, sauf si vous initialisez explicitement les données POD, elles ne sont pas définies alors que d'autres classes ont leur constructeur par défaut appelé automatiquement (même si cela n'a pas été fait explicitement par le programmeur).

Mais en disant ça. Considérez ceci:

template<typename T>
class Z
{
     T  data;   
     Z()
        :data()    // Technicall not need as default constructor will
                   // always be called for classes.
                   // But doing this will initialize POD data correctly
                   // if T is a basic POD type. 
     {}
};

Dans ce cas, les données seraient initialisées par défaut.
Techniquement, les POD n'ont pas de constructeurs, donc si T était int, vous attendriez-vous à ce qu'il fasse quoi que ce soit? Parce qu’il était explicitement initialisé, sa valeur est 0 ou l’équivalent pour les types de POD.

Pour l'édition:

class A
{
    std::map<string,string>   m_someMap;
    int                       m_someint;
    bool                      m_somebool;
   public:
    A::A()
       : m_someMap()      // Class will always be initialised (so optional)
       , m_someint(0)     // without this POD will be undefined
       , m_somebool(false)// without this POD will be undefined
    {}
};

Comme d'autres l'ont fait remarquer: ce n'est pas nécessaire mais plus ou moins une question de style. L'avantage: cela montre que vous voulez explicitement utiliser le constructeur par défaut et rend votre code plus détaillé. Inconvénient: si vous avez plus d’un acteur, il peut être difficile de conserver les modifications dans chacun d’entre eux. Parfois, vous ajoutez des membres de classe et vous oubliez de les ajouter à la liste d’initialisation de ctors et de lui donner un aspect incohérent.

A::A() : m_someMap()

Cette ligne est inutile dans ce cas. Cependant, en général , c’est le seul moyen d’initialiser les membres de la classe.

Si vous avez un constructeur tel que celui-ci:

X() : y(z) {
 w = 42;
}

les événements suivants se produisent lorsque le X constructeur est appelé:

  • Tout d'abord, tous les membres sont initialisés: pour y, nous disons explicitement que nous souhaitons appeler le constructeur qui prend un z comme argument. Pour w, ce qui se produit dépend du type de :. Si m_someMap est un type POD (c’est-à-dire un type compatible C: Pas d’héritage, pas de constructeur ni de destructeur, tous les membres sont publics et tous les membres sont également des types POD), alors ce n'est pas initialisé. Sa valeur initiale correspond à la poubelle trouvée à cette adresse mémoire. Si : m_someMap() est un type non-POD, son constructeur par défaut est appelé (les types non-POD sont toujours initialisés à la construction).
  • Une fois les deux membres construits, nous appelons ensuite l'opérateur d'affectation pour affecter 42 à <=>.

La chose importante à noter est que tous les constructeurs s'appellent avant d'entrer dans le corps du constructeur. Une fois que nous sommes dans le corps, tous les membres ont déjà été initialisés. Il existe donc deux problèmes possibles avec notre constructeur.

  • Et si <=> est d'un type qui n'a pas de constructeur par défaut? Ensuite, cela ne compilera pas. Ensuite, il doit être explicitement initialisé après le <=>, comme <=> l’est.
  • Que se passera-t-il si cette séquence d'appels du constructeur par défaut et de l'opérateur d'affectation à la fois est inutilement lente? Peut-être serait-il beaucoup plus efficace d'appeler simplement le bon constructeur pour commencer.

En bref, étant donné que <=> est un type non POD, nous n’avons pas à le faire à proprement parler <=>. Il aurait été construit par défaut de toute façon. Mais s’il s’agissait d’un type de POD ou si nous avions voulu appeler un autre constructeur que celui par défaut, nous aurions dû le faire.

Juste pour être clair sur ce qui se passe (en ce qui concerne votre deuxième question)

std::map< std::string, std::string > m_someMap crée une variable de pile appelée m_someMap sur laquelle le constructeur par défaut est appelé. La règle pour C ++ pour tous vos objets est si vous allez:

T varName;

où T est un type, nomVar est construit par défaut.

T* varName;

doit être explicitement affecté à NULL (ou 0) - ou à nullptr dans la nouvelle norme.

Pour clarifier le problème de valeur par défaut:

C ++ n'a pas le concept de l'implicité de certains types par référence. À moins que quelque chose ne soit explicitement déclaré en tant que pointeur, il ne peut pas prendre une valeur nulle. Cela signifie que chaque classe aura un constructeur par défaut pour la construction de la valeur initiale quand aucun paramètre de constructeur n'est spécifié. Si aucun constructeur par défaut n'est déclaré, le compilateur en générera un pour vous. De même, chaque fois qu'une classe contient des membres de types classés, ces membres sont implicitement initialisés via leurs propres constructeurs par défaut lors de la construction de l'objet, sauf si vous utilisez la syntaxe du signe deux-points pour appeler explicitement un constructeur différent.

Il se trouve que le constructeur par défaut de tous les types de conteneurs STL crée un conteneur vide. D'autres classes peuvent avoir d'autres conventions pour ce que font leurs constructeurs par défaut, vous devez donc toujours savoir qu'elles sont appelées dans des situations comme celle-ci. C’est pourquoi la ligne A::A() : m_someMap(), qui dit simplement au compilateur de faire ce qu’elle ferait déjà de toute façon.

Lorsque vous créez un objet en C ++, le constructeur exécute la séquence suivante:

  1. Appelez les constructeurs de toutes les classes virtuelles parentes de l'ensemble de l'arborescence de la classe (dans un ordre arbitraire)
  2. Appelez les constructeurs de toutes les classes parent directement héritées dans l'ordre de déclaration
  3. Appelez les constructeurs de toutes les variables membres dans l'ordre de déclaration

Il y a quelques détails plus spécifiques que cela, et certains compilateurs vous permettent de forcer certaines choses hors de cet ordre spécifique, mais c'est l'idée générale. Pour chacun de ces appels de constructeur, vous pouvez spécifier les arguments du constructeur. Dans ce cas, C ++ appellera le constructeur comme spécifié ou vous pouvez le laisser seul et C ++ essaiera d'appeler le constructeur par défaut. Le constructeur par défaut est simplement celui qui ne prend aucun argument.

Si l'une de vos classes parent virtuelle, vos classes parent non virtuelles ou vos variables membres n'ont pas de constructeur par défaut ou doivent être créées avec autre chose que la valeur par défaut, vous les ajoutez à la liste des appels de constructeur. C ++ supposant un appel de constructeur par défaut, il n'y a absolument aucune différence entre placer un constructeur par défaut dans la liste et le laisser entièrement en dehors (C ++ ne créera pas (sauf circonstances exceptionnelles hors du champ de cette question)) la création d'un objet sans appel à un constructeur de quelque sorte). Si une classe n'a pas de constructeur par défaut et que vous ne fournissez pas d'appel de constructeur, le compilateur renvoie une erreur.

Lorsqu'il s'agit de types intégrés tels que float ou int, le constructeur par défaut ne fait rien du tout. La variable aura donc la valeur par défaut de tout ce qui restait dans la mémoire. Tous les types intégrés ont également un constructeur de copie. Vous pouvez donc les initialiser en transmettant leur valeur initiale comme seul argument au constructeur de la variable.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top