style - programação orientada a objetos c++ exercicios resolvidos




Regras alteradas para construtores protegidos em C++ 17? (2)

Eu tenho este caso de teste:

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

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

O gcc e o clang o compilam no modo C ++ 11 e C ++ 14. Ambos falham no modo 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 compilado do 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.

Esta mudança de comportamento faz parte do C ++ 17 ou é um erro em ambos os compiladores?


A definição de aggregate alterada desde C ++ 17.

Antes de C ++ 17

sem classes base

Desde C ++ 17

nenhuma classe base virtual, private, or protected (since C++17)

Isso significa que, para B e D , eles não são do tipo agregado antes de C ++ 17, então para B{} e D{} , value-initialization será executada e, em seguida, o construtor padrão assumido será chamado; o que é bom, porque o construtor protected da classe base poderia ser chamado pelo construtor da classe derivada.

Desde C ++ 17, B e D se tornam tipo agregado (porque eles têm apenas classe base public , e note que para a classe D , o construtor padrão explicitamente padrão é permitido para o tipo agregado desde C ++ 11), então para B{} e D{} , aggregate-initialization será executada,

Cada direct public base, (since C++17) elemento matriz, ou membro de classe não estático, em ordem de matriz subscrito / aparência na definição de classe, é inicializada por cópia a partir da cláusula correspondente da lista de inicializadores.

Se o número de cláusulas do inicializador for menor que o número de membros and bases (since C++17) ou a lista de inicializadores estiver completamente vazia, os membros and bases (since C++17) restantes and bases (since C++17) são inicializados by their default initializers, if provided in the class definition, and otherwise (since C++14) por listas vazias, de acordo com as regras de inicialização de lista usuais (que executa inicialização de valor para classes não agregadas e classes não agregadas com construtores padrão) e inicialização agregada para agregados). Se um membro de um tipo de referência é um desses membros restantes, o programa é mal formado.

Isso significa que o subobjeto da classe base será inicializado por valor diretamente, o construtor de B e D será ignorado; mas o construtor padrão de A é protected , então o código falha. (Observe que A não é A tipo agregado porque tem um construtor fornecido pelo usuário.)

BTW: C (com um construtor fornecido pelo usuário) não é um tipo agregado antes e depois do C ++ 17, portanto, é bom para ambos os casos.


Em C ++ 17, as regras sobre agregados foram alteradas.

Por exemplo, você pode fazer isso em C ++ 17 agora:

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

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

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

Note que não estamos herdando o construtor. Em C ++ 17, C e D agora são agregados, mesmo se tiverem classes base.

Com {} , a inicialização agregada entra em ação e o envio de nenhum parâmetro será interpretado da mesma forma que chamar o construtor padrão do pai a partir do exterior.

Por exemplo, a inicialização agregada pode ser desativada alterando a classe D para isso:

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

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

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

Isso ocorre porque a inicialização agregada não se aplica ao ter membros com diferentes especificadores de acesso.

A razão pela qual com o = default funciona é porque ele não é um construtor fornecido pelo usuário. Mais informações nesta questão .





protected