Por que o construtor sem parâmetros padrão desaparece quando você cria um com parâmetros

Em C #, C ++ e Java, quando você cria um construtor usando parâmetros, o parâmetro sem parâmetros desaparece. Eu sempre aceitei esse fato, mas agora comecei a me perguntar por quê.

Qual é a razão para esse comportamento? É apenas uma “medida / medida de segurança” dizendo “Se você criou um construtor, provavelmente não quer este implícito”? Ou tem uma razão técnica que torna impossível para o compilador adicionar uma vez que você criou um construtor?

Não há nenhuma razão para que o compilador não possa adicionar o construtor se você adicionou o seu próprio – o compilador pode fazer praticamente o que quiser! No entanto, você precisa olhar para o que faz mais sentido:

  • Se eu não tiver definido nenhum construtor para uma class não-estática, é provável que eu seja capaz de instanciar essa class. Para permitir isso, o compilador deve adicionar um construtor sem parâmetros, que não terá nenhum efeito a não ser permitir a instanciação. Isso significa que não preciso include um construtor vazio no meu código apenas para que ele funcione.
  • Se eu defini um construtor próprio, especialmente um com parâmetros, então eu provavelmente tenho minha própria lógica que deve ser executada na criação da class. Se o compilador criasse um construtor vazio, sem parâmetros, neste caso, ele permitiria que alguém ignorasse a lógica que eu havia escrito, o que poderia levar à quebra do meu código de várias maneiras. Se eu quiser um construtor vazio padrão nesse caso, preciso dizê-lo explicitamente.

Assim, em cada caso, você pode ver que o comportamento dos compiladores atuais faz mais sentido em termos de preservar a intenção provável do código.

Certamente não há razão técnica para que a linguagem tenha sido projetada dessa maneira.

Existem quatro opções um pouco realistas que eu posso ver:

  1. Nenhum construtor padrão
  2. O cenário atual
  3. Sempre fornecendo um construtor padrão por padrão, mas permitindo que ele seja explicitamente suprimido
  4. Sempre fornecendo um construtor padrão sem permitir que ele seja suprimido

Opção 1 é um pouco atraente, na medida em que quanto mais código eu menos frequentemente eu quero um construtor sem parâmetros. Algum dia eu deveria contar com que frequência acabo usando um construtor padrão …

Opção 2 Estou bem com.

Opção 3 vai contra o stream de Java e C #, para o resto da linguagem. Não há nada que você explicitamente “remova”, a menos que você conte explicitamente tornar as coisas mais privadas do que seriam, por padrão, em Java.

Opção 4 é horrível – você absolutamente quer ser capaz de forçar a construção com certos parâmetros. O que significaria new FileStream() ?

Então, basicamente, se você aceitar a premissa de que fornecer um construtor padrão faz sentido, acredito que faça muito sentido suprimi-lo assim que você fornecer seu próprio construtor.

Editar. Na verdade, enquanto o que digo na minha primeira resposta é válido, este é o verdadeiro motivo:

No começo havia C. C não é orientado a objects (você pode adotar uma abordagem OO, mas isso não ajuda nem reforça nada).

Em seguida, houve C With Classes, que foi posteriormente renomeado para C ++. C ++ é orientado a object e, portanto, incentiva o encapsulamento e garante a invariante de um object – na construção e no início e no final de qualquer método, o object está em um estado válido.

A coisa natural a fazer com isso, é reforçar que uma class deve sempre ter um construtor para garantir que ele seja iniciado em um estado válido – se o construtor não precisar fazer nada para garantir isso, o construtor vazio documentará esse fato .

Mas um objective com C ++ era ser compatível com C ao ponto em que, tanto quanto possível, todos os programas C válidos também eram programas C ++ válidos (não mais como um objective ativo, e a evolução de C separado para C ++ significa que ele não mais ).

Um efeito disso foi a duplicação da funcionalidade entre struct e class . O primeiro faz as coisas do jeito C (tudo público por padrão) e o último faz as coisas de uma maneira OO boa (tudo privado por padrão, o desenvolvedor ativamente torna público o que eles querem público).

Outra é que, para uma struct C, que não poderia ter um construtor, porque C não tem construtores, para ser válido em C ++, então deveria haver um significado para isso na maneira C ++ de olhar para ele. E assim, apesar de não ter um construtor iria contra a prática OO de garantir ativamente uma invariante, o C ++ levou isto para significar que havia um construtor sem parâmetros padrão que agia como se tivesse um corpo vazio.

Todas as structs C eram agora structs C ++ válidas (o que significava que elas eram as mesmas que as classs C ++ com tudo – membros e inheritance – public) tratados de fora como se tivessem um único construtor sem parâmetros.

Se, no entanto, você colocar um construtor em uma class ou struct , então você estava fazendo as coisas do jeito C ++ / OO em vez do caminho C, e não havia necessidade de um construtor padrão.

Desde que serviu como uma forma abreviada, as pessoas continuavam a usá-lo mesmo quando a compatibilidade não era possível de outra forma (ele usava outros resources do C ++ não em C).

Assim, quando o Java surgiu (baseado em C ++ de várias formas) e posteriormente em C # (baseado em C ++ e Java de maneiras diferentes), eles mantiveram essa abordagem como algo que os programadores já podem estar acostumados.

Stroustrup escreve sobre isso em seu The C ++ Programming Language e, mais ainda, com mais foco sobre os “porquês” da linguagem em The Design and Evolution of C ++ .

=== Resposta Original ===

Vamos dizer que isso não aconteceu.

Digamos que eu não queira um construtor sem parâmetros, porque não posso colocar minha class em um estado significativo sem uma. Na verdade, isso é algo que pode acontecer com o struct em C # (mas se você não pode fazer uso significativo de uma estrutura all-zeros-and-nulls em C # você está usando uma otimização não visível publicamente, e de outra forma tem uma falha de design no uso de struct ).

Para tornar minha class capaz de proteger suas invariantes, preciso de uma palavra-chave especial removeDefaultConstructor . No mínimo, eu preciso criar um construtor privado sem parâmetros para garantir que nenhum código de chamada chame o padrão.

O que complica a linguagem um pouco mais. Melhor não fazer isso.

Em geral, é melhor não pensar em adicionar um construtor como remover o padrão, é melhor pensar em não ter nenhum construtor como um acréscimo sintático para adicionar um construtor sem parâmetros que não faz nada.

O construtor sem parâmetros e padrão é adicionado se você não fizer nada sozinho para assumir o controle da criação de objects. Depois de criar um único construtor para assumir o controle, o compilador “recua” e permite que você tenha o controle total.

Se não fosse assim, você precisaria de alguma maneira explícita de desabilitar o construtor padrão se quiser que apenas objects sejam construtíveis por meio de um construtor com parâmetros.

É uma function de conveniência do compilador. Se você definir um construtor com parâmetros, mas não definir um construtor sem parâmetros, a possibilidade de não permitir um construtor sem parâmetros é muito maior.

Este é o caso de muitos objects que simplesmente não fazem sentido inicializar com um construtor vazio.

Caso contrário, você teria que declarar um construtor privado sem parâmetros para cada class que deseja restringir.

Na minha opinião, não é um bom estilo permitir um construtor sem parâmetros para uma class que precisa de parâmetros para funcionar.

Eu acho que a pergunta deve ser o contrário: por que você não precisa declarar um construtor padrão se você não definiu nenhum outro construtor?

Um construtor é obrigatório para classs não estáticas.
Então eu acho que se você não definiu nenhum construtor, o construtor padrão gerado é apenas um recurso conveniente do compilador C #, também sua class não seria válida sem um construtor. Portanto, nada de errado com a geração implícita de um construtor que não faz nada. Certamente parece mais limpo do que ter construtores vazios ao redor.

Se você já definiu um construtor, sua class é válida, então por que o compilador deve assumir que você quer um construtor padrão? E se você não quiser um? Implementar um atributo para dizer ao compilador para não gerar esse construtor padrão? Eu não acho que seria uma boa ideia.

O construtor padrão pode ser construído somente quando a class não possui um construtor. Compiladores são escritos de tal forma a fornecer isso apenas como um mecanismo de backup.

Se você tiver um construtor com parâmetros, talvez não queira que um object seja criado usando o construtor padrão. Se o compilador tivesse fornecido um construtor padrão, você teria que escrever um construtor no-arg e torná-lo privado para evitar que objects fossem criados sem argumentos.

Além disso, haveria maiores chances de você esquecer a desativação ou “privatizar” o construtor padrão e, assim, causar um possível erro funcional difícil de capturar.

E agora você precisa definir explicitamente um construtor no-arg se quiser que um object seja criado da forma padrão ou passando parâmetros. Isso é altamente verificado, e o compilador reclama de outra forma, garantindo assim que não haja lacunas aqui.

Premissa

Esse comportamento pode ser visto como uma extensão natural da decisão de que as classs tenham um construtor público sem parâmetros padrão . Com base na pergunta que nos foi feita, tomamos essa decisão como uma premissa e assumimos que não a estamos questionando neste caso.

Maneiras de remover o construtor padrão

Segue-se que deve haver uma maneira de remover o construtor público sem parâmetros padrão. Essa remoção pode ser realizada das seguintes maneiras:

  1. Declarar um construtor sem parâmetros não público
  2. Remover automaticamente o construtor sem parâmetros quando um construtor com parâmetros é declarado
  3. Alguma palavra-chave / atributo para indicar ao compilador para remover o construtor sem parâmetros (estranho o suficiente para que seja fácil descartar)

Selecionando a Melhor Solução

Agora nos perguntamos: Se não há construtor sem parâmetros, o que deve ser substituído? e em que tipos de cenários queremos remover o construtor público sem parâmetros padrão?

As coisas começam a cair no lugar. Em primeiro lugar, deve ser substituído por um construtor com parâmetros ou por um construtor não público. Em segundo lugar, os cenários sob os quais você não deseja um construtor sem parâmetros são:

  1. Não queremos que a class seja instanciada, ou queremos controlar a visibilidade do construtor: declarar um construtor não público
  2. Queremos forçar os parâmetros a serem fornecidos na construção: declarar um construtor com parâmetros

Conclusão

Lá nós o temos – exatamente as duas maneiras pelas quais C #, C ++ e Java permitem a remoção do construtor público sem parâmetros padrão.

Eu acho que isso é tratado pelo compilador. Se você abrir o assembly .net no ILDASM , verá o construtor padrão, mesmo que não esteja no código. Se você definir um construtor parametrizado, o construtor padrão não será visualizado.

Na verdade, quando você define a class (não estática), o compilador fornece esse recurso pensando que você estará criando apenas uma instância. E se você quiser que qualquer operação específica seja realizada, certamente terá seu próprio construtor.

É porque quando você não define um construtor, o compilador gera automaticamente um construtor para você que não aceita nenhum argumento. Quando você quer algo mais fora de um construtor, você supera isso. Isso não é sobrecarga de function. Portanto, o único construtor que o compilador vê agora é seu construtor, que recebe um argumento. Para combater esse problema, você pode passar um valor padrão se o construtor for passado sem valor.