web-dev-qa-db-fra.com

Règles modifiées pour les constructeurs protégés en C ++ 17?

J'ai ce cas de test:

struct A{ protected: A(){} };
struct B: A{};
struct C: A{ C(){} };
struct D: A{ D() = default; };

int main(){
    (void)B{};
    (void)C{};
    (void)D{};
}

Gcc et clang le compilent en mode C++ 11 et C++ 14. Les deux échouent en mode C++ 17:

$ clang++ -std=c++17 main.cpp 
main.cpp:7:10: error: base class 'A' has protected default constructor
        (void)B{};
                ^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
                     ^
main.cpp:9:10: error: base class 'A' has protected default constructor
        (void)D{};
                ^
main.cpp:1:22: note: declared protected here
struct A{ protected: A(){} };
                     ^
2 errors generated.

$ clang++ --version
clang version 6.0.0 (http://llvm.org/git/clang.git 96c9689f478d292390b76efcea35d87cbad3f44d) (http://llvm.org/git/llvm.git 360f53a441902d19ce27d070ad028005bc323e61)
Target: x86_64-unknown-linux-gnu
Thread model: posix

(Clang compilé à partir de Master Branch 2017-12-05.)

$ g++ -std=c++17 main.cpp 
main.cpp: In function 'int main()':
main.cpp:7:10: error: 'A::A()' is protected within this context
  (void)B{};
          ^
main.cpp:1:22: note: declared protected here
 struct A{ protected: A(){} };
                      ^
main.cpp:9:10: error: 'A::A()' is protected within this context
  (void)D{};
          ^
main.cpp:1:22: note: declared protected here
 struct A{ protected: A(){} };
                      ^

$ g++ --version
g++ (GCC) 8.0.0 20171201 (experimental)
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Ce changement de comportement fait-il partie de C++ 17 ou s'agit-il d'un bogue dans les deux compilateurs?

60
Benjamin Buch

La définition de global a changé depuis C++ 17.

Avant C++ 17

pas de classes de base

Depuis C++ 17

pas de virtual, private, or protected (since C++17) classes de base

Cela signifie que pour B et D, ils ne sont pas de type agrégé avant C++ 17, puis pour B{} Et D{}, initialisation de la valeur sera effectuée, puis le constructeur par défaut par défaut sera appelé; ce qui est bien, car le constructeur protected de la classe de base peut être appelé par le constructeur de la classe dérivée.

Depuis C++ 17, B et D deviennent un type d'agrégat (car ils n'ont que public classe de base, et notez que pour la classe D, le explicitement le constructeur par défaut par défaut est autorisé pour le type d'agrégat depuis C++ 11), puis pour B{} et D{}, initialisation d'agrégat sera effectué,

Chaque élément de tableau direct public base, (since C++17) ou membre de classe non statique, par ordre d'indice/apparence du tableau dans la définition de classe, est initialisé en copie à partir de la clause correspondante de la liste d'initialisation.

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 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). Si un membre d'un type de référence est l'un de ces membres restants, le programme est mal formé.

Cela signifie que le sous-objet de la classe de base sera directement initialisé en valeur, le constructeur de B et D est contourné; mais le constructeur par défaut de A est protected, puis le code échoue. (Notez que A n'est pas de type agrégé car il a un constructeur fourni par l'utilisateur.)

BTW: C (avec un constructeur fourni par l'utilisateur) n'est pas un type d'agrégat avant et après C++ 17, donc c'est bien pour les deux cas.

51
songyuanyao

En C++ 17, les règles concernant les agrégats ont changé.

Par exemple, vous pouvez le faire en C++ 17 maintenant:

struct A { int a; };
struct B { B(int){} };

struct C : A {};
struct D : B {};

int main() {
    (void) C{2};
    (void) D{1};
}

Notez que nous n'héritons pas du constructeur. En C++ 17, C et D sont maintenant des agrégats même s'ils ont des classes de base.

Avec {}, l'initialisation d'agrégation entre en jeu et l'envoi d'aucun paramètre sera interprété de la même façon que l'appel du constructeur par défaut du parent depuis l'extérieur.

Par exemple, l'initialisation agrégée peut être désactivée en modifiant la classe D en ceci:

struct B { protected: B(){} };

struct D : B {
    int b;
private:
    int c;
};

int main() {
    (void) D{}; // works!
}

En effet, l'initialisation agrégée ne s'applique pas lorsque des membres ont des spécificateurs d'accès différents.

La raison pour laquelle avec = default fonctionne parce que ce n'est pas un constructeur fourni par l'utilisateur. Plus d'informations sur cette question .

22
Guillaume Racicot