Constructeur vide par défaut étrange sur un comportement d'héritage virtuel sur GCC

Dec 14 2020

J'ai la situation suivante d'une classe dérivée avec héritage virtuel à une classe de base dans mon code:

class Base {
    int x;
public:
    Base(int x): x{x} {}
    virtual void f() = 0;
};

class Derived : public virtual Base  {
  public:
    Derived() = default;
};

class Concrete: public Derived {
public:
    Concrete(): Base{42} {}
    void f() override {}
};

Lien: https://godbolt.org/z/bn1EY6

GCC (trunk) donne l'erreur suivante: error: use of deleted function 'Derived::Derived()'tandis que Clang (trunk) le compile sans problème.

GCC fonctionne si je change le constructeur en au Derived() {}lieu de Derived() = defaultou si je définis un constructeur vide sur la classe Base.

Pourquoi = defaultsupprimer la fonction dans GCC dans ce cas?

Réponses

2 eerorika Dec 14 2020 at 18:29

Standard dit (dernier projet):

[class.default.ctor]

Un constructeur par défaut pour la classe X est défini comme supprimé si:

  • X est une union qui ... [[ne s'applique pas]]
  • X est une classe non-union qui a un membre variant M avec ... [[ne s'applique pas]]
  • tout membre de données non statique sans initialiseur de membre par défaut ([class.mem]) est de type référence, [[ne s'applique pas]]
  • tout membre de données non statique non variant de type qualifié const ... [[ne s'applique pas]]
  • X est une union et ... [[ne s'applique pas]]
  • X est une classe non syndiquée et tous les membres d'un syndicat anonyme ... [[ne s'applique pas]]
  • [s'applique si la base est un sous-objet potentiellement construit ] tout sous-objet potentiellement construit, à l'exception d'un membre de données non statique avec un initialiseur d'accolade ou égal, a le type de classe M (ou un tableau de celui-ci) et soit M n'a pas de constructeur par défaut ou la résolution de surcharge ([over.match]) appliquée pour trouver le constructeur correspondant de M entraîne une ambiguïté ou une fonction qui est supprimée ou inaccessible du constructeur par défaut par défaut, ou
  • tout sous-objet potentiellement construit a un type avec un destructeur qui est supprimé ou inaccessible à partir du constructeur par défaut par défaut. [[Ne s'applique pas]]

Une seule règle s'applique potentiellement pour le constructeur par défaut par défaut en cours de suppression, et cela dépend du fait que la base est un sous-objet potentiellement construit .

[spécial]

Pour une classe, ses membres de données non statiques, ses classes de base directes non virtuelles et, si la classe n'est pas abstraite ([class.abstract]), ses classes de base virtuelles sont appelées ses sous-objets potentiellement construits.

Derivedest abstraite (car elle n'implémente pas toutes les fonctions virtuelles pures), et Baseest une base virtuelle, donc la base n'est pas un sous-objet potentiellement construit, et par conséquent la seule règle qui aurait autrement appliqué pour le constructeur par défaut supprimé ne s'applique pas et il ne devrait donc pas être supprimé. Le compilateur est faux.


Une solution de contournement simple (en plus de celles que vous avez déjà mentionnées) consiste à ne pas déclarer Derived::Derieved()du tout. Il semble être correctement généré implicitement dans ce cas.


L'ajout de noexcept génère l'erreur d'erreur interne du compilateur

C'est aussi un bogue du compilateur.

1 AdrianMole Dec 14 2020 at 18:02

Pourquoi le = default supprime-t-il la fonction dans GCC dans ce cas?

Que ce soit ou non un bogue dans GCC (MSVC se comporte de la même manière mais clang-cl accepte le code, tel quel) est une question pour ceux qui sont plus étudiés dans les standards C ++. Cependant, il semble que le complicateur considère = defaultque le Derivedconstructeur dépend (ou équivaut à ) le constructeur par défaut pour Base- qui est définitivement supprimé, car vous avez défini un autre constructeur (non par défaut).

Cependant, ajouter explicitement votre propre constructeur par défaut, avec Derived() {}supprime cette dépendance implicite.

Ceci est confirmé (dans GCC et MSVC) en spécifiant (c'est-à-dire en annulant) le constructeur par défaut pour la Baseclasse:

class Base {
    int x;
public:
    Base() : x{0} {}  // Adding this removes the error!
//  Base() = default; // Also works
    Base(int x): x{x} {}
    virtual void f() = 0;
};

class Derived : public virtual Base  {
  public:
    Derived() = default;
};

class Concrete: public Derived {
public:
    Concrete(): Base{42} {}
    void f() override {}
};

EDIT : Cela peut également être pertinent, voire un doublon possible: Pourquoi le constructeur par défaut est-il appelé dans l'héritage virtuel?