web-dev-qa-db-fra.com

L'initialisation d'agrégats C++ peut-elle être utilisée pour construire une instance d'une classe implémentant une interface?

J'espère que quelqu'un pourra me donner les détails techniques de la raison pour laquelle les éléments suivants ne seront pas compilés et, si possible, d'une solution de rechange.

J'ai une structure existante appelée Foo et un code qui utilise des listes d'initialisation pour créer des instances de Foo. Ce code compile et fonctionne:

struct Foo {
    int id1;
    int id2;
};

int main()
{
    Foo f({1,2});

    return f.id1;
}

J'aimerais que Foo mette en place une interface:

struct Interface {
    // All pure virtual methods, but this won't compile even if empty
};

struct Foo : public Interface{
    int id1;
    int id2;
};

int main()
{
    Foo f({1,2});

    return f.id1;
}

Ce code ne compile plus, avec des erreurs dans la veine de 

cannot convert argument 1 from 'initializer list' to 'const _Ty &'

(L'erreur change en fonction de votre compilateur exact.)

J'ai trouvé cette section de la norme relative à l'initialisation d'agrégat:

[dcl.init.aggr]/1 Un agrégat est un tableau ou une classe (clause 12) avec 1.1 aucun constructeur fourni par l'utilisateur, explicite ou hérité (15.1), 1.2 pas de données privées ni de données membres non statiques protégées (clause 14), 1.3 pas de fonctions virtuelles (13.3), et 1.4 pas de classes de base virtuelles, privées ou protégées (13.1).

Bien que je ne sois pas vraiment sûr si l'initialisation globale est ce qui se passe ici. Quelqu'un peut-il expliquer l'erreur qui se produit et, si possible, proposer des modifications que je pourrais apporter à l'interface? J'ai plusieurs structures existantes qui ont besoin de cette interface et beaucoup de code existant qui utilise cette forme d'initialisation, et j'aimerais en réécrire le moins possible. Je vous remercie!

8
ConShonnery

Vous devez initialiser la classe de base même si elle est vide:

Foo f({{},1,2});

le voir en direct sur godbolt

Plus bas dans la norme de la section à laquelle vous faites référence, nous en voyons un exemple dans [dcl.init.aggr] p4.2 :

struct base1 { int b1, b2 = 42; };
struct base2 {
  base2() {
   b3 = 42;
 }
 int b3;
};

struct derived : base1, base2 {
 int d;
};

derived d1{{1, 2}, {}, 4};
derived d2{{}, {}, 4};

initialise d1.b1 avec 1, d1.b2 avec 2, d1.b3 avec 42, d1.d avec 4, et d2.b1 avec 0, d2.b2 avec 42, d2.b3 avec 42, d2.d avec 4. —end Exemple]

Voir aussi [dcl.init.aggr] p2 qui explique quels sont les éléments d'un agrégat:

Les éléments d'un agrégat sont:

-pour un tableau, les éléments du tableau en ordre croissant d'indice, ou
-pour une classe, les classes de base directes dans l'ordre de déclaration, suivies de les membres de données non statiques directs ([class.mem]) qui ne sont pas membres d'un syndicat anonyme, dans l'ordre de déclaration.

et [dcl.init.aggr] p3 dit:

Lorsqu'un agrégat est initialisé par une liste d'initialisateurs comme spécifié dans [dcl.init.list], les éléments de la liste d'initialiseurs sont considérés comme des initialiseurs pour les éléments de l'agrégat. ...

Notez que la réponse suppose C++ 17 ou supérieur puisque avant C++ 17, un agrégat n'était pas autorisé à avoir une classe de base .

9
Shafik Yaghmour

@ShafikYaghmour a expliqué pourquoi, lorsque Interface est vide, l'initialisation de l'agrégat ne peut plus être effectuée comme auparavant.

Mais si Interface a des fonctions virtuelles, comme suggéré dans la question, la classe dérivée de Interface ne sera pas un agrégat . Ainsi, la classe qui implémente Interface et contient les membres de données en tant que Foo doit implémenter un constructeur. Le moyen le plus simple que je vois (qui, en fonction de la "trivialité" des données membres, ne soit pas forcément la plus efficace en termes de rapidité) est le suivant:

struct Interface {
   // All pure virtual methods, but this won't compile even if empty
   virtual void bar() =0;
   };

struct Foo_data{ //change the name of the aggregate
  int id1;
  int id2;
  };

struct Foo
  :Interface  //Foo has virtual function => Foo is not an aggregate
  ,Foo_data
  {
  Foo() =default;
  Foo(Foo_data data):Foo_data(std::move(data)){}//a constructor must be provided
  void bar() override {}
  };

int main(){
  Foo f({1,2});
  return f.id1;
  }
1
Oliv