Digamos que eu tenha um tipo e desejo que seu construtor padrão seja privado. Eu escrevo o seguinte:
class C {
C() = default;
};
int main() {
C c; // error: C::C() is private within this context (g++)
// error: calling a private constructor of class 'C' (clang++)
// error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC)
auto c2 = C(); // error: as above
}
Ótimo.
Mas então, o construtor acabou não sendo tão privado quanto eu pensava que era:
class C {
C() = default;
};
int main() {
C c{}; // OK on all compilers
auto c2 = C{}; // OK on all compilers
}
Isso me parece um comportamento muito surpreendente, inesperado e explicitamente indesejado. Por que isso está OK?
C c{};
a inicialização de agregação, então nenhum construtor é chamado?C
é um agregado.=default
ctor público , isso pareceria mais razoável. Mas o=default
ctor privado parece algo importante que não deve ser ignorado. Além do mais,class C { C(); } inline C::C()=default;
ser bem diferente é um tanto surpreendente.Respostas:
O truque está em C ++ 14 8.4.2 / 5 [dcl.fct.def.default]:
O que significa que
C
o construtor padrão não é fornecido pelo usuário, porque foi explicitamente padronizado em sua primeira declaração. Como tal,C
não tem construtores fornecidos pelo usuário e, portanto, é um agregado por 8.5.1 / 1 [dcl.init.aggr]:fonte
C{}
funciona mesmo se o construtor fordelete
d.Você não está chamando o construtor padrão, está usando a inicialização de agregação em um tipo de agregação. Os tipos agregados podem ter um construtor padrão, contanto que seja padronizado onde for declarado pela primeira vez:
De [dcl.init.aggr] / 1 :
e de [dcl.fct.def.default] / 5
Assim, nossos requisitos para um agregado são:
C
cumpre todos esses requisitos.Naturalmente, você pode se livrar desse falso comportamento de construção padrão simplesmente fornecendo um construtor padrão vazio ou definindo o construtor como padrão após declará-lo:
fonte
De Angew e jaggedSpire's ' respostas de são excelentes e se aplicam ac ++ 11. Ec ++ 14. Ec ++ 17.
No entanto, em c ++ 20, as coisas mudam um pouco e o exemplo no OP não compilará mais:
Conforme apontado pelas duas respostas, o motivo pelo qual as duas últimas declarações funcionam é porque
C
é um agregado e esta é uma inicialização de agregado. No entanto, como resultado de P1008 (usando um exemplo motivador não muito diferente do OP), a definição de alterações agregadas em C ++ 20 para, de [dcl.init.aggr] / 1 :Ênfase minha. Agora, o requisito não é nenhum construtor declarado pelo usuário , ao passo que costumava ser (como ambos os usuários citam em suas respostas e pode ser visto historicamente para C ++ 11 , C ++ 14 e C ++ 17 ) nenhum construtor fornecido pelo usuário . O construtor padrão para
C
é declarado pelo usuário, mas não fornecido pelo usuário e, portanto, deixa de ser um agregado em C ++ 20.Aqui está outro exemplo ilustrativo de alterações agregadas:
B
não era um agregado em C ++ 11 ou C ++ 14 porque tem uma classe base. Como um resultado,B{}
apenas invoca o construtor padrão (declarado pelo usuário, mas não fornecido pelo usuário), que tem acesso aoA
construtor padrão protegido de.Em C ++ 17, como resultado de P0017 , os agregados foram estendidos para permitir classes básicas.
B
é um agregado em C ++ 17, o que significa queB{}
é uma inicialização de agregação que deve inicializar todos os subobjetos - incluindo oA
subobjeto. Mas, comoA
o construtor padrão de é protegido, não temos acesso a ele, então essa inicialização está malformada.No C ++ 20, por causa do
B
construtor declarado pelo usuário, ele novamente deixa de ser uma agregação, entãoB{}
volta a invocar o construtor padrão e esta é novamente uma inicialização bem formada.fonte