web-dev-qa-db-fra.com

"temporaire de type 'A' a protégé le destructeur", mais son type est B

Dans le code suivant, compilé avec Clang 8.0.0+ et -std=c++17, création d'une instance de classe dérivée à l'aide de B{} donne une erreur error: temporary of type 'A' has protected destructor. Pourquoi A apparaît-il dans ce message lorsque le temporaire a le type B (et devrait donc avoir un destructeur public)?

https://godbolt.org/z/uOzwYa

class A {
protected:
    A() = default;
    ~A() = default;
};

class B : public A {
// can also omit these 3 lines with the same result
public:
    B() = default;
    ~B() = default;
};

void foo(const B&) {}

int main() {

    // error: temporary of type 'A' has protected destructor
    foo(B{});
    //    ^

    return 0;
}
10
jtbandes

Il s'agit d'un problème subtil de initialisation agrégée avant C++ 20.

Avant C++ 20, B (et A) sont types d'agrégats :

(c'est moi qui souligne)

aucun constructeur fourni par l'utilisateur, hérité ou explicite ( les constructeurs explicitement par défaut ou supprimés ne sont autorisés ) (depuis C++ 17) (jusqu'à C++ 20 )

Ensuite

Si le nombre de clauses d'initialisation est inférieur au nombre de membres and bases (since C++17) ou que la liste d'initialisation est complètement vide, les membres restants and bases (since C++17) sont initialisés by their default member initializers, if provided in the class definition, and otherwise (since C++14) par des listes vides, conformément aux règles habituelles d'initialisation de liste (qui effectue l'initialisation de la valeur pour les types non classe et les classes non agrégées avec des constructeurs par défaut, et l'initialisation agrégée pour les agrégats).

Donc B{} Construit un objet temporaire via l'initialisation agrégée, qui initialisera le sous-objet de base directement avec une liste vide, c'est-à-dire effectuera l'initialisation agrégée pour construire le sous-objet de base A. Notez que le constructeur de B est contourné. Le problème est que dans un tel contexte, le descripteur protected ne peut pas être appelé pour détruire le sous-objet de base directement construit de type A. (Il ne se plaint pas du constructeur protected car il est également contourné par l'initialisation globale de A.)

Vous pouvez le changer en foo(B()); pour éviter l'initialisation agrégée; B() effectue valeur-initialisation , l'objet temporaire sera initialisé par le constructeur de B, alors tout va bien.

BTW depuis C++ 20, votre code fonctionnera correctement.

aucun constructeur déclaré ou hérité par l'utilisateur (depuis C++ 20)

B (et A) ne sont plus des types agrégés. B{} Exécute initialisation de la liste , puis l'objet temporaire est initialisé par le constructeur de B; l'effet est identique à B().

11
songyuanyao