Supposons que j'ai une classe avec des membres privés ptr
, name
, pname
, rname
, crname
et age
. Que se passe-t-il si je ne les initialise pas moi-même? Voici un exemple:
class Example {
private:
int *ptr;
string name;
string *pname;
string &rname;
const string &crname;
int age;
public:
Example() {}
};
Et puis je fais:
int main() {
Example ex;
}
Comment les membres sont-ils initialisés dans ex? Qu'est-ce qui se passe avec les pointeurs? Est-ce que string
et int
ont une initialisation 0 avec les constructeurs par défaut string()
et int()
? Qu'en est-il du membre de référence? Et qu'en est-il des références const?
Que devrais-je savoir d'autre?
Est-ce que quelqu'un connaît un tutoriel qui couvre ces cas? Peut-être dans certains livres? Dans la bibliothèque de l'université, j'ai accès à beaucoup de livres en C++.
J'aimerais l'apprendre pour pouvoir écrire de meilleurs programmes (sans bug). Tout commentaire aiderait!
Au lieu d'une initialisation explicite, l'initialisation des membres dans les classes fonctionne de manière identique à l'initialisation des variables locales dans les fonctions.
Pour objets, leur constructeur par défaut est appelé. Par exemple, pour std::string
, le constructeur par défaut le définit sur une chaîne vide. Si la classe de l'objet ne possède pas de constructeur par défaut, ce sera une erreur de compilation si vous ne l'initialisez pas explicitement.
Pour types primitifs (pointeurs, entiers, etc.), ils ne sont pas initialisés - ils contiennent tout le matériel indésirable arbitraire qui s'est passé à cet emplacement de mémoire précédemment.
Pour références (par exemple, std::string&
), il est illégal de ne pas les initialiser et votre compilateur se plaindra et refusera de compiler ce code. Les références doivent toujours être initialisées.
Donc, dans votre cas spécifique, s'ils ne sont pas explicitement initialisés:
int *ptr; // Contains junk
string name; // Empty string
string *pname; // Contains junk
string &rname; // Compile error
const string &crname; // Compile error
int age; // Contains junk
Tout d'abord, laissez-moi vous expliquer ce qu'est une mem-initializer-list . Une mem-initializer-list est une liste séparée par des virgules de mem-initializers, où chacun mem-initializer est un nom de membre suivi de (
, suivi d'une liste d'expressions , suivi d'un )
. La liste d'expressions est la façon dont le membre est construit. Par exemple, dans
static const char s_str[] = "bodacydo";
class Example
{
private:
int *ptr;
string name;
string *pname;
string &rname;
const string &crname;
int age;
public:
Example()
: name(s_str, s_str + 8), rname(name), crname(name), age(-4)
{
}
};
la mem-initializer-list du constructeur sans argument fourni par l'utilisateur est name(s_str, s_str + 8), rname(name), crname(name), age(-4)
. Ceci mem-initializer-list signifie que le membre name
est initialisé par le constructeur std::string
Qui prend deux itérateurs d’entrée , le membre rname
est initialisé avec une référence à name
, le membre crname
est initialisé avec une référence const à name
et le membre age
est initialisé avec la valeur -4
.
Chaque constructeur a sa propre mem-initializer-list , et les membres ne peuvent être initialisés que dans un ordre déterminé (essentiellement l'ordre dans lequel les membres sont déclarés). la classe). Ainsi, les membres de Example
ne peuvent être initialisés que dans l'ordre suivant: ptr
, name
, pname
, rname
, crname
et age
.
Lorsque vous ne spécifiez pas un mem-initializer d'un membre, la norme C++ dit:
Si l'entité est un membre de données non statique ... de type classe ..., l'entité est initialisée par défaut (8.5). ... Sinon, l'entité n'est pas initialisée.
Ici, étant donné que name
est un membre de données non statique de type classe, il est initialisé par défaut si aucun initialiseur pour name
n'a été spécifié dans le mem-initializer- liste . Tous les autres membres de Example
n'ont pas de type de classe, ils ne sont donc pas initialisés.
Lorsque la norme indique qu'elle n'est pas initialisée, cela signifie qu'elle peut avoir n'importe quelle valeur . Ainsi, étant donné que le code ci-dessus n'a pas initialisé pname
, il peut s'agir de n'importe quoi.
Notez que vous devez toujours suivre d'autres règles, telles que la règle selon laquelle les références doivent toujours être initialisées. C'est une erreur du compilateur de ne pas initialiser les références.
Vous pouvez également initialiser les membres de données au moment où vous les déclarez:
class another_example{
public:
another_example();
~another_example();
private:
int m_iInteger=10;
double m_dDouble=10.765;
};
J'utilise cette forme presque exclusivement, bien que j'ai lu certaines personnes qui la considèrent comme une "mauvaise forme", peut-être parce qu'elle n'a été introduite que récemment - je pense en C++ 11. Pour moi, c'est plus logique.
Une autre facette utile des nouvelles règles est la procédure d'initialisation des membres de données qui sont eux-mêmes des classes. Par exemple, supposons que CDynamicString
soit une classe qui encapsule le traitement des chaînes. Il a un constructeur qui vous permet de spécifier sa valeur initiale CDynamicString(wchat_t* pstrInitialString)
. Vous pouvez très bien utiliser cette classe en tant que membre de données dans une autre classe. Par exemple, une classe qui encapsule une valeur de registre Windows qui, dans ce cas, stocke une adresse postale. Pour 'coder en dur' le nom de la clé de registre sur laquelle ceci vous écrit, utilisez des accolades:
class Registry_Entry{
public:
Registry_Entry();
~Registry_Entry();
Commit();//Writes data to registry.
Retrieve();//Reads data from registry;
private:
CDynamicString m_cKeyName{L"Postal Address"};
CDynamicString m_cAddress;
};
Notez que la deuxième classe de chaîne qui contient l'adresse postale réelle n'a pas d'initialiseur, son constructeur par défaut sera donc appelé à la création - peut-être en le définissant automatiquement comme chaîne vide.
Si votre exemple de classe est instancié sur la pile, le contenu des membres scalaires non initialisés est aléatoire et non défini.
Pour une instance globale, les membres scalaires non initialisés seront mis à zéro.
Pour les membres qui sont eux-mêmes des instances de classes, leurs constructeurs par défaut seront appelés, ainsi votre objet string sera initialisé.
int *ptr;
// pointeur non initialisé (ou mis à zéro si global)string name;
// constructeur appelé, initialisé avec une chaîne videstring *pname;
// pointeur non initialisé (ou mis à zéro si global)string &rname;
// erreur de compilation si vous ne parvenez pas à l'initialiserconst string &crname;
// erreur de compilation si vous ne parvenez pas à l'initialiserint age;
// valeur scalaire, non initialisée et aléatoire (ou mise à zéro si globale)Les membres non statiques non initialisés contiendront des données aléatoires. En fait, ils auront juste la valeur de l'emplacement mémoire auquel ils sont assignés.
Bien sûr, pour les paramètres d'objet (comme string
), le constructeur de l'objet peut effectuer une initialisation par défaut.
Dans votre exemple:
int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value
Les membres avec un constructeur auront leur constructeur par défaut appelé pour l'initialisation.
Vous ne pouvez pas dépendre du contenu des autres types.
S'il se trouve sur la pile, le contenu des membres non initialisés qui n'ont pas leur propre constructeur sera aléatoire et indéfini. Même si c'est global, ce serait une mauvaise idée de compter sur leur mise à zéro. Que ce soit sur la pile ou non, si un membre a son propre constructeur, il sera appelé pour l'initialiser.
Ainsi, si vous avez la chaîne * pname, le pointeur contiendra des fichiers aléatoires. mais pour nom de chaîne, le constructeur par défaut de chaîne sera appelé, ce qui vous donnera une chaîne vide. Pour les variables de type référence, je ne suis pas sûr, mais ce sera probablement une référence à une partie aléatoire de la mémoire.