O padrão C ++ (seção 8.5) diz:
Se um programa pede a inicialização padrão de um objeto de um tipo T qualificado por const, T deve obrigatoriamente ser um tipo de classe com um construtor padrão fornecido pelo usuário.
Por quê? Não consigo pensar em nenhuma razão pela qual um construtor fornecido pelo usuário seja necessário neste caso.
struct B{
B():x(42){}
int doSomeStuff() const{return x;}
int x;
};
struct A{
A(){}//other than "because the standard says so", why is this line required?
B b;//not required for this example, just to illustrate
//how this situation isn't totally useless
};
int main(){
const A a;
}
a
, mas gcc-4.3.4 aceita mesmo quando você o faz (consulte ideone.com/uHvFS )a
. Comeau produz um erro "variável const" a "requer um inicializador - a classe" A "não tem nenhum construtor padrão declarado explicitamente" se a linha estiver comentada.const A a{}
:)Respostas:
Isso foi considerado um defeito (em todas as versões do padrão) e foi resolvido pelo Defeito 253 do Core Working Group (CWG) . A nova redação para os estados padrão em http://eel.is/c++draft/dcl.init#7
Esta formulação significa essencialmente que o código óbvio funciona. Se você inicializar todas as suas bases e membros, você pode dizer
A const a;
independentemente de como ou se soletrou quaisquer construtores.O gcc aceitou isso desde 4.6.4. O clang aceita isso desde 3.9.0. O Visual Studio também aceita isso (pelo menos em 2017, não tenho certeza se antes).
fonte
struct A { int n; A() = default; }; const A a;
enquanto permite,struct B { int n; B() {} }; const B b;
porque a nova redação ainda diz "fornecido pelo usuário" e não "declarado pelo usuário" e estou coçando minha cabeça por que o comitê escolheu excluir construtores padrão explicitamente inadimplentes deste DR, nos forçando a fazer nossas classes não são triviais se quisermos objetos const com membros não inicializados.MyPOD
sendo um PODstruct
,static MyPOD x;
- contando com zero de inicialização (que é o caminho certo?) Para definir a variável de membro (s) de forma apropriada - compila, masstatic const MyPOD x;
não o faz. Existe alguma chance de que isso seja consertado?O motivo é que, se a classe não tiver um construtor definido pelo usuário, pode ser POD, e a classe POD não é inicializada por padrão. Portanto, se você declarar um objeto const de POD que não foi inicializado, qual a utilidade disso? Portanto, acho que o Standard impõe essa regra para que o objeto possa realmente ser útil.
Mas se você fizer da aula um não-POD:
Observe a diferença entre POD e não-POD.
O construtor definido pelo usuário é uma maneira de tornar a classe não POD. Existem várias maneiras de fazer isso.
Observe que nonPOD_B não define o construtor definido pelo usuário. Compile-o. Ele irá compilar:
E comente a função virtual, então dá erro, conforme o esperado:
Bem, eu acho, você entendeu mal a passagem. Primeiro diz isto (§8.5 / 9):
Ele fala sobre a classe não-POD, possivelmente do tipo cv qualificado . Ou seja, o objeto não-POD deve obrigatoriamente ser inicializado por padrão se não houver um inicializador especificado. E o que é inicializado por padrão ? Para não POD, a especificação diz (§8.5 / 5),
Ele simplesmente fala sobre o construtor padrão de T, seja seu definido pelo usuário ou gerado pelo compilador é irrelevante.
Se você está claro para isso, entenda o que a especificação diz a seguir ((§8.5 / 9),
Portanto, este texto implica que o programa será malformado se o objeto for do tipo POD qualificado const e não houver um inicializador especificado (porque os POD não são inicializados por padrão):
A propósito, ele compila bem , porque não é POD e pode ser inicializado por padrão .
fonte
nonPOD_B
não tem um construtor padrão fornecido pelo usuário, então a linhaconst nonPOD_B b2
não é permitida.B
na questão). Mas o construtor padrão fornecido pelo usuário ainda é necessário nesse caso.const
objeto não-POD seja inicializado chamando o construtor padrão gerado pelo compilador.Pura especulação da minha parte, mas considere que outros tipos também têm uma restrição semelhante:
Portanto, essa regra não é apenas consistente, mas também (recursivamente) evita
const
(sub) objetos unitializados :Quanto ao outro lado da questão (permitindo-o para tipos com um construtor padrão), acho que a ideia é que um tipo com um construtor padrão fornecido pelo usuário deve sempre estar em algum estado lógico após a construção. Observe que as regras permitem o seguinte:
Então, talvez pudéssemos formular uma regra como 'pelo menos um membro deve ser inicializado sensatamente em um construtor padrão fornecido pelo usuário', mas isso é muito tempo gasto tentando proteger contra Murphy. C ++ tende a confiar no programador em certos pontos.
fonte
A(){}
, o erro vai embora, por isso não impede nada. A regra não funciona recursivamente -X(){}
nunca é necessária para esse exemplo.Eu estava assistindo a palestra de Timur Doumler no Meeting C ++ 2018 e finalmente percebi por que o padrão requer um construtor fornecido pelo usuário aqui, não apenas um declarado pelo usuário. Tem a ver com as regras de inicialização de valor.
Considere duas classes:
A
tem um construtor declarado pelo usuário ,B
tem um construtor fornecido pelo usuário :À primeira vista, você pode pensar que esses dois construtores se comportarão da mesma forma. Mas veja como a inicialização de valor se comporta de maneira diferente, enquanto apenas a inicialização padrão se comporta da mesma forma:
A a;
é a inicialização padrão: o membroint x
não foi inicializado.B b;
é a inicialização padrão: o membroint x
não foi inicializado.A a{};
é a inicialização do valor: o membroint x
é inicializado com zero .B b{};
é a inicialização do valor: o membroint x
não foi inicializado.Agora veja o que acontece quando adicionamos
const
:const A a;
é a inicialização padrão: está mal formada devido à regra citada na pergunta.const B b;
é a inicialização padrão: o membroint x
não foi inicializado.const A a{};
é a inicialização do valor: o membroint x
é inicializado com zero .const B b{};
é a inicialização do valor: o membroint x
não foi inicializado.Um
const
escalar não inicializado (por exemplo, oint x
membro) seria inútil: escrever nele é malformado (porque éconst
) e ler a partir dele é UB (porque contém um valor indeterminado). Então Esta regra impede de criar uma coisa dessas, forçando você a quer adicionar um initialiser ou opt-in para o comportamento perigoso, adicionando um construtor fornecido pelo usuário.Acho que seria bom ter um atributo como
[[uninitialized]]
avisar ao compilador quando você não está inicializando intencionalmente um objeto. Então, não seríamos forçados a tornar nossa classe não trivialmente construtível por padrão para contornar esse caso. Este atributo já foi proposto , mas assim como todos os outros atributos padrão, não impõe nenhum comportamento normativo, sendo apenas uma dica para o compilador.fonte
Parabéns, você inventou um caso em que não precisa haver nenhum construtor definido pelo usuário para que a
const
declaração sem inicializador faça sentido.Agora, você pode propor uma reformulação razoável da regra que cubra seu caso, mas ainda torne os casos que deveriam ser ilegais, ilegais? Tem menos de 5 ou 6 parágrafos? É fácil e óbvio como deve ser aplicado em qualquer situação?
Acredito que criar uma regra que permita que a declaração que você criou faça sentido é muito difícil, e certificar-se de que a regra pode ser aplicada de uma forma que faça sentido para as pessoas ao ler o código é ainda mais difícil. Eu preferiria uma regra um tanto restritiva que fosse a coisa certa a fazer na maioria dos casos a uma regra muito sutil e complexa que fosse difícil de entender e aplicar.
A questão é: há uma razão convincente para que a regra seja mais complexa? Existe algum código que de outra forma seria muito difícil de escrever ou entender que pode ser escrito de forma muito mais simples se a regra for mais complexa?
fonte
const POD x;
ilegal, assim comoconst int x;
é ilegal (o que faz sentido, porque isso é inútil para um POD), mas seriaconst NonPOD x;
legal (o que faz sentido, porque poderia ter subobjetos contendo construtores / destruidores úteis, ou ter um construtor / destruidor útil) .struct NonPod { std::string s; }; const NonPod x;
e dá um erro quando NonPod éstruct NonPod { int i; std::string s; }; const NonPod x;