23 votes

Constructeur de l'héritage et membre direct de l'initialisation

Je suis en train d'utiliser une combinaison de C++ 11 direct de données des membres de l'initialisation et l'aide de la syntaxe d'hériter les constructeurs d'une classe de base. Maintenant avec gcc 5.4.0 (sur Ubuntu 16.04), j'ai observé un étrange erreur, si le membre de données de type n'a pas de constructeur par défaut. Il est probablement plus facile à comprendre lorsque l'on cherche sur le après un exemple:

#include <iostream>

struct Foo {
  Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; }
};

struct Base {
  Base(int arg) { std::cout << "Base::Base(" << arg << ")" << std::endl; }
};

struct Derived : public Base {
  using Base::Base;
  Foo foo{42};
};

int main() {
  Derived derived{120};
}

Ce code compile et s'exécute avec le comportement attendu avec clang. Avec gcc, il ne compile pas, parce que le compilateur supprime le constructeur Derived::Derived(int):

ttt.cpp: In function ‘int main()':
ttt.cpp:17:22: error: use of deleted function ‘Derived::Derived(int)'
   Derived derived{120};
                      ^
ttt.cpp:12:15: note: ‘Derived::Derived(int)' is implicitly deleted because the default definition would be ill-formed:
   using Base::Base;
               ^
ttt.cpp:12:15: error: no matching function for call to ‘Foo::Foo()'
ttt.cpp:4:3: note: candidate: Foo::Foo(int)
   Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; }
   ^
ttt.cpp:4:3: note:   candidate expects 1 argument, 0 provided
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(const Foo&)
 struct Foo {
        ^
ttt.cpp:3:8: note:   candidate expects 1 argument, 0 provided
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(Foo&&)
ttt.cpp:3:8: note:   candidate expects 1 argument, 0 provided

Si j'ajoute un constructeur par défaut de Foo comme ceci:

  Foo() { std::cout << "Foo::Foo()" << std::endl; };

aussi gcc pouvez le compiler. Le code se comporte exactement de la même manière, en particulier l'ajout de constructeur par défaut de Foo n'est jamais exécutée.

Donc ma question est maintenant, est-ce valable en C++ 11? Si oui, j'ai sans doute trouvé un bug de gcc. Sinon, ne devrait pas gcc et clang me donner un message d'erreur que ce n'est pas valide en C++ 11?

Edit après la question a été gentiment répondu par @vlad-de-moscou: Ce bug semble être présent également dans gcc 6.2, donc je vais faire un rapport de bogue.

2ème edit: Il y a déjà un bug que je n'ai pas trouvé lors de la première recherche: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054

11voto

Vlad from Moscow Points 36219

La gcc ne satisfait pas à la Norme C++. Hérité du constructeur de la classe Dérivée doit appeler le constructeur de Base dans ses mem-liste d'initialiseur avec l'argument spécifié pour la Dérivée hérité de constructeur.

Il est écrit dans la Norme C++ (12.9 Hériter constructeur)

8 Une hériter de constructeur d'une classe se définit implicitement quand il est-odr-utilisé (3.2) pour créer un objet de son type de classe (1.8). Un implicitement défini hériter constructeur effectue l'ensemble de l'initialisation de la classe qui serait effectuée par un utilisateur écrit inline constructeur pour cette classe avec un mem-initialiseur-liste dont seulement mem-initialiseur a un mem-initialiseur-id que les noms de la base de la classe dénotée dans la nested-nom-spécificateur de l'aide-déclaration et une expression de la liste comme indiqué ci-dessous, et où la composé déclaration dans son corps de la fonction est vide (12.6.2). Si c' écrit par l'utilisateur constructeur serait mal formé, le programme est mal formé. Chaque expression dans l'expression de la liste est de la forme static_cast(p), où p est le nom du correspondant paramètre du constructeur et T est le type déclaré de p.

Aussi, conformément à la section (12.6.2 Initialisation des bases et des membres)

8 Dans un non-la délégation de constructeur, si un non-membre de données statiques ou de la classe de base n'est pas désigné par un mem-initialiseur-id (y compris les cas où il n'y a pas de mem-initialiseur-liste car le constructeur a noctor-initialiseur) et l'entité n'est pas une classe de base virtuelle de une classe abstraite (10.4), puis

- si l'entité est un non-membre de données statiques qui a un corset ou égal initialiseur, l'entité est initialisé comme spécifié dans 8.5;

5voto

AndyG Points 3298

Il semble que vous avez raison, il y a un bug de gcc

De l'article 12.9 [classe.inhctor]:

Un aide-déclaration (7.3.3), que le nom d'un constructeur déclare implicitement un ensemble de héritant des constructeurs. L' ensemble candidat de héritées des constructeurs de la classe X nommée dans l' aide-déclaration contient des les constructeurs et théorique des constructeurs qui résultent de la transformation de la souffrance paramètres comme suit:

  • tous les non-modèle constructeurs d' X

Cela signifie donc que votre Derived classe devrait definnitely obtenir un constructeur de sa base qui accepte un int. En suivant les règles normales de classe de l'initialisation de membre, la construction d'une instance de Derived ne devrait pas être un problème sans un constructeur par défaut pour Foo , car il n'est pas utilisé. Donc, il y a un bug de gcc:

§13.3.1.3 d'Initialisation du constructeur [plus.match.ctor]

Lorsque les objets de type classe sont directe-initialisé (8.5) [...], la résolution de surcharge sélectionne le constructeur. Directe pour l'initialisation, le candidat les fonctions sont tous les constructeurs de la classe de l'objet en cours d'initialisation.

Ainsi, le constructeur Foo::Foo(int) doivent avoir été sélectionnés, ce qui clairement n'était pas dans gcc.


Une question que j'ai eu après avoir lu que c'était "est-ce à cause le constructeur par défaut pour Derived être supprimé?" La réponse est non.

Idéalement, la norme fournit un exemple ci-dessous cet extrait (je suis ce que l'excision n'est pas nécessaire):

struct B1 {
   B1(int);
};

struct D1 : B1 {
   using B1::B1;
};

L'ensemble des constructeurs présents en D1 est [l'Accent de la mine]

  • D1(), implicitement déclarées constructeur par défaut, mal formé si odr-utilisé
  • D1(const D1&), implicitement déclarées constructeur de copie, pas héréditaire
  • D1(D1&&), implicitement déclarées constructeur de déplacement, pas héréditaire
  • D1(int), implicitement déclarées hériter constructeur

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