web-dev-qa-db-fra.com

Liste d'initialisation des membres C ++ 11 vs initialiseur en classe?

Quelle différence entre ces façons d'initialiser des variables de membre d'objet en C++ 11? Y a-t-il un autre moyen? quelle voie est meilleure (performance)?:

class any {
  public:
    obj s = obj("value");
    any(){}
};

Ou

class any {
  public:
    obj s;
    any(): s("value"){}
};

Merci.

35
Ahmed T. Ali

Non, ce sont pas les mêmes.

La différence entre eux est la même que celle qui s'applique pour initialisation directe vs initialisation de copie , ce qui est subtil mais souvent très déroutant.

§12.6.2 [class.base.init]:

  1. La expression-list ou braced-init-list dans un mem-initializer est utilisé pour initialiser le sous-objet désigné (ou, dans le cas d'un constructeur délégué, l'objet de classe complet) selon les règles d'initialisation de 8.5 pour initialisation directe. [...]

  2. Dans un constructeur non délégué, si un membre de données non statique ou une classe de base donné n'est pas désigné par un mem-initializer-id (y compris le cas où il n'y a pas mem-initializer-list car le constructeur n'a pas ctor-initializer ) et l'entité n'est pas une classe de base virtuelle d'une classe abstraite (10.4), alors

    - si l'entité est un membre de données non statique qui a un accolade-ou-égal-initialiseur , l'entité est initialisée comme spécifié en 8.5;

§8.5 [dcl.init]:

  1. L'initialisation qui se produit dans le formulaire

    T x = a;

ainsi que dans le passage d'arguments, le retour de fonction, le lancement d'une exception (15.1), la gestion d'une exception (15.3) et l'initialisation d'agrégats de membres (8.5.1) est appelée copie-initialisation.

L'initialisation d'un membre de données non statique sur une liste d'initialiseurs de membres suit les règles de initialisation directe , qui ne crée pas de temporaires intermédiaires qui doivent être déplacés/copiés (si compilés sans copie-élision ), ni le type du membre de données ne doit être copiable/mobile (même si la copie est élidée). De plus, une initialisation directe introduit un contexte explicite, tandis qu'une copie-initialisation n'est pas explicite (si un constructeur sélectionné pour l'initialisation est explicit, le programme ne compilera pas).

En d'autres termes, la syntaxe obj s = obj("value"); ne se compilera pas si obj est déclarée comme:

struct obj
{
    obj(std::string) {}
    obj(const obj&) = delete;
};

ou:

struct obj
{
    obj(std::string) {}
    explicit obj(const obj&) {}
};

Comme exemple plus tangible, alors que ce qui suit ne se compilera pas:

struct any
{
   std::atomic<int> a = std::atomic<int>(1); // ill-formed: non-copyable/non-movable
   std::atomic<int> b = 2; // ill-formed: explicit constructor selected
};

celui-ci:

struct any
{
    std::atomic<int> a;
    std::atomic<int> b{ 2 };
    any() : a(1) {}
};

Quelle est la meilleure (performance)?

Avec une copie-élision activée, les deux ont des performances identiques. Lorsque copy-elision est désactivé, il y a un appel supplémentaire de constructeur copy/move à chaque instanciation lorsque initialisation de copie (== --- ==) la syntaxe est utilisée (dont obj s = obj("value"); en est une).


Existe-t-il un autre moyen?

La syntaxe accolade-ou-égaliseur-initialiseur permet d'effectuer une initialisation de liste directe ainsi:

class any {
public:
    obj s{ "value" };
    any() {}
};

Y a-t-il d'autres différences?

Quelques autres différences qui méritent d'être mentionnées sont:

  1. L'initialiseur d'accolade ou égal doit résider dans un fichier d'en-tête avec une déclaration de classe.
  2. Si les deux sont combinés, list-initializer-list a priorité sur accolade-ou-égal-initialiseur (c'est-à-dire que l'initialisation d'accolade ou égale est ignorée).
  3. (C++ 11 uniquement, jusqu'à C++ 14) Une classe qui utilise un accolade ou un initialiseur égal viole les contraintes d'un type d'agrégat.
  4. Avec la syntaxe accolade-ou-égal-initialiseur , il n'est pas possible d'effectuer une initialisation directe autre qu'une initialisation de liste directe .
38
Piotr Skotnicki

Les deux exemples sont équivalents.
Bien que seulement si le type est copiable ou mobile (vérifiez par vous-même) et NRVO est réellement fait (tout compilateur décent à mi-chemin le fera d'office).

Bien que si vous aviez de nombreux constructeurs et que le chaînage de constructeurs soit inapproprié, la première méthode vous permettrait de ne pas vous répéter.

En outre, vous pouvez utiliser cette méthode pour définir des agrégats avec des valeurs par défaut différentes de l'initialisation d'agrégat pour (certains) membres depuis C++ 14.

7
Deduplicator

Ce sont les mêmes.

Aucun n'est meilleur que l'autre en termes de performances, et il n'y a pas d'autre moyen de les initialiser.

L'avantage de l'initialisation en classe (le premier dans votre exemple) est que l'ordre d'initialisation est implicite. Dans la liste des initialiseurs, vous devez indiquer explicitement l'ordre - et les compilateurs avertiront d'une initialisation hors service si vous obtenez un ordre incorrect.

De la norme:

12.6.2.5
nonstatic data members shall be initialized in the order they were declared 
in the class definition

Si la commande est incorrecte dans votre liste, GCC se plaindra:

main.cpp: In constructor 'C::C()':
main.cpp:51:9: warning: 'C::b' will be initialized after
main.cpp:51:6: warning:   'int C::a'

L'avantage des listes d'initialisation est peut-être une question de goût - la liste est explicite, généralement dans le fichier source. In-class est implicite (sans doute) et se trouve généralement dans le fichier d'en-tête.

3
Steve Lorimer