Dans ce cas spécifique, y a-t-il une différence entre l'utilisation d'une liste d'initialiseurs de membres et l'affectation de valeurs dans un constructeur?

90

En interne et à propos du code généré, y a-t-il vraiment une différence entre:

MyClass::MyClass(): _capacity(15), _data(NULL), _len(0)
{
}

et

MyClass::MyClass()
{
  _capacity=15;
  _data=NULL;
  _len=0
}

Merci...

Stef
la source

Réponses:

60

En supposant que ces valeurs sont des types primitifs, alors non, il n'y a pas de différence. Les listes d'initialisation ne font une différence que lorsque vous avez des objets en tant que membres, car au lieu d'utiliser l'initialisation par défaut suivie d'une affectation, la liste d'initialisation vous permet d'initialiser l'objet à sa valeur finale. Cela peut en fait être sensiblement plus rapide.

templatetypedef
la source
17
comme Richard l'a dit, cela fait une différence si ces valeurs sont des types primitifs et const, les listes d'initialisation sont le seul moyen d'attribuer des valeurs aux membres const.
thbusch
11
Cela ne fonctionne comme décrit que lorsque les variables ne sont pas des références ou des constantes, si elles sont constantes ou des références, elles ne seront même pas compilées sans utiliser la liste d'initialisation.
stefanB du
77

Vous devez utiliser la liste d'initialisation pour initialiser les membres constants, les références et la classe de base

Lorsque vous devez initialiser un membre constant, des références et passer des paramètres aux constructeurs de classe de base, comme mentionné dans les commentaires, vous devez utiliser la liste d'initialisation.

struct aa
{
    int i;
    const int ci;       // constant member

    aa() : i(0) {} // will fail, constant member not initialized
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0) { ci = 3;} // will fail, ci is constant
};

struct aa
{
    int i;
    const int ci;

    aa() : i(0), ci(3) {} // works
};

L'exemple (non exhaustif) class / struct contient une référence:

struct bb {};

struct aa
{
    bb& rb;
    aa(bb& b ) : rb(b) {}
};

// usage:

bb b;
aa a(b);

Et exemple d'initialisation de la classe de base qui nécessite un paramètre (par exemple pas de constructeur par défaut):

struct bb {};

struct dd
{
    char c;
    dd(char x) : c(x) {}
};

struct aa : dd
{
    bb& rb;
    aa(bb& b ) : dd('a'), rb(b) {}
};
stefanB
la source
4
Et si _capacity, _dataet _lenavoir des types de classe sans constructeurs par défaut accessibles?
CB Bailey
2
Vous appelez quels constructeurs sont disponibles, si vous avez besoin de définir plus de valeurs, vous les appelez à partir du corps de votre constructeur. La différence ici est que vous ne pouvez pas initialiser le constmembre dans le corps du constructeur, vous devez utiliser la liste d'initialisation - les non constmembres peuvent être initialisés dans la liste d'initialisation ou dans le corps du constructeur.
stefanB
@stefan: Vous ne pouvez pas appeler de constructeurs. Une classe sans constructeur par défaut doit être initialisée dans la liste d'initialisation, tout comme les membres const.
GManNickG
2
vous devez également initialiser les références dans la liste d'initialisation
Andriy Tylychko
2
@stefanB: Mes excuses si j'ai laissé entendre que vous ne comprenez pas cela alors que vous le faites réellement. Dans votre réponse, vous avez seulement indiqué quand une base ou un membre doit être nommé dans la liste d'initialisation, vous n'avez pas expliqué quelle est la différence conceptuelle entre l'initialisation dans la liste d'initialisation et l'affectation dans le corps du constructeur, c'est là que mon malentendu peut provenir de.
CB Bailey
18

Oui. Dans le premier cas, vous pouvez déclarer _capacity, _dataet _lencomme constantes:

class MyClass
{
private:
    const int _capacity;
    const void *_data;
    const int _len;
// ...
};

Cela serait important si vous souhaitez vous assurer de la constvalidité de ces variables d'instance lors du calcul de leurs valeurs au moment de l'exécution, par exemple:

MyClass::MyClass() :
    _capacity(someMethod()),
    _data(someOtherMethod()),
    _len(yetAnotherMethod())
{
}

constles instances doivent être initialisées dans la liste d'initialisation ou les types sous-jacents doivent fournir des constructeurs publics sans paramètre (ce que font les types primitifs).

Richard Cook
la source
2
Il en va de même pour les références. Si votre classe a des membres de référence, ils doivent être initialisés dans la liste d'initialisation.
Mark Ransom
7

Je pense que ce lien http://www.cplusplus.com/forum/articles/17820/ donne une excellente explication - en particulier pour ceux qui découvrent le C ++.

La raison pour laquelle les listes d'initialisation sont plus efficaces est que dans le corps du constructeur, seules les affectations ont lieu, pas l'initialisation. Donc, si vous avez affaire à un type non intégré, le constructeur par défaut de cet objet a déjà été appelé avant que le corps du constructeur n'ait été entré. Dans le corps du constructeur, vous attribuez une valeur à cet objet.

En fait, il s'agit d'un appel au constructeur par défaut suivi d'un appel à l'opérateur d'affectation de copie. La liste d'initialisation vous permet d'appeler directement le constructeur de copie, et cela peut parfois être beaucoup plus rapide (rappelez-vous que la liste d'initialisation est avant le corps du constructeur)

user929404
la source
5

J'ajouterai que si vous avez des membres de type classe sans constructeur par défaut disponible, l'initialisation est le seul moyen de construire votre classe.

Fred Larson
la source
3

Une grande différence est que l'affectation peut initialiser les membres d'une classe parent; l'initialiseur ne fonctionne que sur les membres déclarés dans la portée de classe actuelle.

Mark Ransom
la source
2

Dépend des types impliqués. La différence est similaire entre

std::string a;
a = "hai";

et

std::string a("hai");

où la deuxième forme est une liste d'initialisation, c'est-à-dire que cela fait une différence si le type nécessite des arguments de constructeur ou est plus efficace avec des arguments de constructeur.

Chiot
la source
1

La vraie différence se résume à la façon dont le compilateur gcc génère le code machine et installe la mémoire. Explique:

  • (phase1) Avant le corps d'initialisation (y compris la liste d'initialisation): le compilateur alloue la mémoire requise pour la classe. La classe est déjà vivante!
  • (phase2) Dans le corps d'initialisation: la mémoire étant allouée, chaque affectation indique maintenant une opération sur la variable déjà sortie / 'initialisée'.

Il existe certainement d'autres façons de gérer les membres de type const. Mais pour faciliter leur vie, les auteurs du compilateur gcc décident de mettre en place des règles

  1. Les membres de type const doivent être initialisés avant le corps d'initialisation.
  2. Après la phase1, toute opération d'écriture n'est valide que pour les membres non constants.
Lukmac
la source
1

Il n'y a qu'une seule façon d' initialiser les instances de classe de base et les variables de membre non statiques et qui utilise la liste d'initialisation.

Si vous ne spécifiez pas de variable membre de base ou non statique dans la liste d'initialisation de votre constructeur, ce membre ou cette base sera initialisé par défaut (si le membre / base est un type de classe non-POD ou un tableau de classe non-POD types) ou laissés non initialisés dans le cas contraire.

Une fois le corps du constructeur entré, toutes les bases ou tous les membres auront été initialisés ou laissés non initialisés (c'est-à-dire qu'ils auront une valeur indéterminée). Il n'y a aucune possibilité dans le corps du constructeur d'influencer la façon dont ils doivent être initialisés.

Vous pourrez peut-être attribuer de nouvelles valeurs aux membres dans le corps du constructeur, mais il n'est pas possible d'affecter des constmembres ou des membres de type classe qui ont été rendus non assignables et il n'est pas possible de relier les références.

Pour les types intégrés et certains types définis par l'utilisateur, l'affectation dans le corps du constructeur peut avoir exactement le même effet que l'initialisation avec la même valeur dans la liste d'initialisation.

Si vous ne parvenez pas à nommer un membre ou une base dans une liste d'initialiseurs et que cette entité est une référence, a un type de classe sans constructeur par défaut déclaré par l'utilisateur accessible, est constqualifiée et a un type POD ou est un type de classe POD ou un tableau de type de classe POD contenant un constmembre qualifié (directement ou indirectement) alors le programme est mal formé.

CB Bailey
la source
0

Si vous écrivez une liste d'initialiseurs, vous faites tout en une seule étape; si vous n'écrivez pas de liste d'initiateurs, vous ferez 2 étapes: une pour la déclaration et une pour attribuer la valeur.

L. Vicente Mangas
la source
0

Il existe une différence entre la liste d'initialisation et l'instruction d'initialisation dans un constructeur. Considérons le code ci-dessous:

#include <initializer_list>
#include <iostream>
#include <algorithm>
#include <numeric>

class MyBase {
public:
    MyBase() {
        std::cout << __FUNCTION__ << std::endl;
    }
};

class MyClass : public MyBase {
public:
    MyClass::MyClass() : _capacity( 15 ), _data( NULL ), _len( 0 ) {
        std::cout << __FUNCTION__ << std::endl;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

class MyClass2 : public MyBase {
public:
    MyClass2::MyClass2() {
        std::cout << __FUNCTION__ << std::endl;
        _capacity = 15;
        _data = NULL;
        _len = 0;
    }
private:
    int _capacity;
    int* _data;
    int _len;
};

int main() {
    MyClass c;
    MyClass2 d;

    return 0;
}

Lorsque MyClass est utilisé, tous les membres seront initialisés avant la première instruction dans un constructeur exécuté.

Mais, lorsque MyClass2 est utilisé, tous les membres ne sont pas initialisés lorsque la première instruction d'un constructeur est exécutée.

Dans un cas ultérieur, il peut y avoir un problème de régression lorsque quelqu'un a ajouté du code dans un constructeur avant qu'un certain membre ne soit initialisé.

user928143
la source
0

Voici un point que je n'ai pas vu d'autres y faire référence:

class temp{
public:
   temp(int var);
};

La classe temporaire n'a pas de cteur par défaut. Lorsque nous l'utilisons dans une autre classe comme suit:

class mainClass{
public:
 mainClass(){}
private:
  int a;
  temp obj;
};

le code ne compilera pas, car le compilateur ne sait pas comment initialiser obj, car il a juste un ctor explicite qui reçoit une valeur int, nous devons donc changer le ctor comme suit:

mainClass(int sth):obj(sth){}

Donc, il ne s'agit pas seulement de const et de références!

hosh0425
la source