48 votes

L'extension C ++ 17 à l'initialisation agrégée a-t-elle rendu l'initialisation de l'accolade dangereuse?

Il semble y avoir un consensus général sur l' accolade d'initialisation doit être privilégiée par rapport à d'autres formes d'initialisation, cependant, depuis l'introduction du C++17 extension globale de l'initialisation il semble y avoir un risque de non intentionnels des conversions. Considérons le code suivant:

struct B { int i; };
struct D : B { char j; };
struct E : B { float k; };

void f( const D& d )
{
  E e1 = d;   // error C2440: 'initializing': cannot convert from 'D' to 'E'
  E e2( d );  // error C2440: 'initializing': cannot convert from 'D' to 'E'
  E e3{ d };  // OK in C++17 ???
}

struct F
{
  F( D d ) : e{ d } {}  // OK in C++17 ???
  E e;
};

Dans le code ci-dessus struct D et struct E représentent les deux complètement différents types. C'est donc une surprise pour moi que de C++17 il est possible de "convertir" d'un type vers un autre type sans aucun avertissement si vous utilisez corset (globale) de l'initialisation.

Que recommanderiez-vous pour éviter ces types d'accident conversions? Ou ai-je raté quelque chose?

PS: Le code ci-dessus a été testé dans Clang, GCC et la dernière VC++ - ils sont tous les mêmes.

Mise à jour: En réponse à la réponse de Nicol. Un exemple pratique:

struct point { int x; int y; };
struct circle : point { int r; };
struct rectangle : point { int sx; int sy; };

void move( point& p );

void f( circle c )
{
  move( c ); // OK, makes sense
  rectangle r1( c );  // Error, as it should be
  rectangle r2{ c };  // OK ???
}

Je peux comprendre que vous pouvez afficher un circle comme pointcar circle a point comme classe de base, mais l'idée que vous pouvez en silence convertir à partir d'un cercle, un rectangle, pour moi, c'est un problème.

Mise à jour 2: Parce que mon mauvais choix de nom de la classe semble être opacification le problème pour certains.

struct shape { int x; int y; };
struct circle : shape { int r; };
struct rectangle : shape { int sx; int sy; };

void move( shape& p );

void f( circle c )
{
  move( c ); // OK, makes sense
  rectangle r1( c );  // Error, as it should be
  rectangle r2{ c };  // OK ???
}

37voto

Nicol Bolas Points 133791

struct D et struct E représentent les deux complètement différents types.

Mais elles ne sont pas complètement indépendants" types. Ils ont tous deux le même type de classe. Cela signifie que chaque D est implicitement convertible pour un B. Et par conséquent, chaque D est B. Ce faisant, E e{d}; n'est pas différent de E e{b}; en termes de invoqué l'opération.

Vous ne pouvez pas désactiver la conversion implicite aux classes de base.

Si cette vérité vous dérange, la seule solution est d'empêcher l'ensemble de l'initialisation en fournissant un constructeur approprié(s) qui transmet les valeurs pour les membres.

Quant à savoir si cela fait de agrégé de l'initialisation de plus dangereux, je ne le pense pas. Vous pourriez reproduire les circonstances ci-dessus avec ces structures:

struct B { int i; };
struct D { B b; char j; operator B() {return b;} };
struct E { B b; float k; };

Donc, quelque chose de cette nature a toujours été une possibilité. Je ne pense pas que l'utilisation implicite de la classe de base de conversion le fait que beaucoup de "pire".

Une question plus profonde est pourquoi un utilisateur a essayé d'initialiser un E avec un D pour commencer.

l'idée que vous pouvez en silence convertir à partir d'un cercle, un rectangle, pour moi, c'est un problème.

Vous avez le même problème si vous n'avez ceci:

struct rectangle
{
  rectangle(point p);

  int sx; int sy;
  point p;
};

Vous pouvez non seulement effectuer rectangle r{c}; mais rectangle r(c).

Votre problème est que vous êtes en utilisant l'héritage de manière incorrecte. Vous êtes en train de dire des choses à propos de la relation entre circle, rectangle et point qui vous ne voulez pas dire. Et par conséquent, le compilateur permet de faire des choses que tu ne voulais pas faire.

Si vous aviez utilisé l'enceinte de confinement à la place de l'héritage, ce ne serait pas un problème:

struct point { int x; int y; };
struct circle { point center; int r; };
struct rectangle { point top_left; int sx; int sy; };

void move( point& p );

void f( circle c )
{
  move( c ); // Error, as it should, since a circle is not a point.
  rectangle r1( c );  // Error, as it should be
  rectangle r2{ c };  // Error, as it should be.
}

Soit circle est toujours un point, ou c'est jamais une point. Vous êtes à essayer de faire un point parfois et pas d'autres. C'est logiquement incohérente. Si vous créez logiquement incohérente types, alors vous pouvez écrire logiquement incohérente code.


l'idée que vous pouvez en silence convertir à partir d'un cercle, un rectangle, pour moi, c'est un problème.

Ceci nous amène à un point important. La Conversion, à proprement parler, ressemble à ceci:

circle cr = ...
rectangle rect = cr;

Qui est mal formé. Lorsque vous effectuez rectangle rect = {cr};, vous êtes pas de faire une conversion. Vous êtes explicitement l'ouverture de la liste d'initialisation, ce qui, pour un montant total sera généralement provoquer globale de l'initialisation.

Maintenant, la liste d'initialisation peut certainement effectuer une conversion. Mais simplement D d = {e};, il ne faut pas s'attendre à ce que cela signifie que vous êtes l'exécution de la conversion à partir d'un e d'un D. Vous êtes à la liste d'initialisation d'un objet de type D avec un e. Que peut convertir les fichiers si E convertible en D, mais cette initialisation peut encore être valide en cas de non-conversion de la liste d'initialisation formes peut fonctionner aussi.

Donc, il est inexact de dire que cette caractéristique rend circle convertibles rectangle.

29voto

Barry Points 45207

Ce n'est pas nouveau en C++17. Agrégation de l'initialisation de toujours vous a permis de laisser de côté les membres (ce qui serait initialisé à partir d'un vide d'initialiseur de liste, C++11):

struct X {
    int i, j;
};

X x{42}; // ok in C++11

Et c'est seulement maintenant qu'il y a plus de sortes de choses qui pourraient être de gauche, car il y a plus de sortes de choses qui peuvent être inclus.


gcc et clang au moins donnera un avertissement par voie d' -Wmissing-field-initializers (c'est une partie de l' -Wextra) qui indiquent que quelque chose est manquante. Si c'est un énorme souci, juste compiler avec cet avertissement est activé (et, éventuellement, de la mise à niveau d'une erreur):

<source>: In function 'void f(const D&)':
<source>:9:11: warning: missing initializer for member 'E::k' [-Wmissing-field-initializers]
   E e3{ d };  // OK in C++17 ???
           ^
<source>: In constructor 'F::F(D)':
<source>:14:19: warning: missing initializer for member 'E::k' [-Wmissing-field-initializers]
   F( D d ) : e{ d } {}  // OK in C++17 ???
                   ^

Plus direct serait juste pour ajouter un constructeur à ces types pour qu'ils cessent d'être des agrégats. Vous ne pas avoir à utiliser l'ensemble de l'initialisation, après tout.

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