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

161

Em C #, C ++ e Java, quando você cria um construtor que aceita parâmetros, o padrão sem parâmetros desaparece. Sempre aceitei esse fato, mas agora comecei a me perguntar por quê.

Qual o motivo desse comportamento? É apenas uma "medida / palpite de segurança" dizendo "Se você criou um construtor próprio, provavelmente não quer este implícito por aí"? Ou ele tem uma razão técnica que torna impossível para o compilador adicionar uma depois que você mesmo cria um construtor?

olagjo
fonte
7
Você pode adicionar C ++ à lista de idiomas que possuem esse comportamento.
Henk Holterman
18
E em C ++, agora você pode dizer Foo() = default;para recuperar o padrão.
MSalters
1
Se o seu construtor com parâmetros puder ter argumentos padrão para todos os seus parâmetros, ele entrará em conflito com o construtor sem parâmetros interno, daí a necessidade de excluí-lo quando você criar o seu.
Morwenn 3/08
3
Imagine estar presente neste debate entre os fundadores do primeiro compilador para fazer o requisito de construtor padrão e a discussão acalorada que ele teria inspirado.
kingdango
7
O @HenkHolterman C ++ não é apenas outro com esse comportamento, mas o originador, e é para permitir compatibilidade com C, conforme discutido por Stroustrup em Design e evolução do C ++ e resumido na minha resposta atualizada.
Jon Hanna

Respostas:

219

Não há razão para que o compilador não possa adicionar o construtor se você adicionou o seu - o compilador pode fazer praticamente o que quiser! No entanto, você deve observar o que faz mais sentido:

  • Se eu não defini nenhum construtor para uma classe não estática, provavelmente desejo instanciar essa classe. Para permitir isso, o compilador deve adicionar um construtor sem parâmetros, que não terá efeito senão permitir instanciação. Isso significa que não preciso incluir um construtor vazio no meu código apenas para fazê-lo funcionar.
  • Se eu defini um construtor próprio, especialmente um com parâmetros, provavelmente tenho uma lógica própria que deve ser executada na criação da classe. Se o compilador criar um construtor vazio e sem parâmetros nesse caso, isso permitirá que alguém pule a lógica que eu escrevi, o que pode levar à quebra do código de várias maneiras. Se eu quiser um construtor vazio padrão nesse caso, preciso dizer isso explicitamente.

Portanto, 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.

Dan Puzey
fonte
2
Acho que o resto da sua resposta prova que sua primeira frase está errada.
quer tocar hoje
76
@KonradRudolph, a primeira frase diz um compilador poderia adicionar um construtor neste cenário - o resto da resposta explica por que não (para os idiomas especificados na pergunta)
johnl
1
Bem não. Se projetamos uma linguagem OO do zero, o significado mais óbvio de não haver construtor é "você esqueceu de adicionar um construtor que garanta a classe 'invariável" e isso geraria um erro de compilação.
Jon Hanna
70

Certamente não há razão técnica para o idioma ter que ser projetado dessa maneira.

Existem quatro opções um tanto realistas que 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 seja explicitamente suprimido
  4. Sempre fornecendo um construtor padrão sem permitir que seja suprimido

A opção 1 é um pouco atraente, pois quanto mais eu codigo, menos frequentemente quero realmente um construtor sem parâmetros. Algum dia, devo contar quantas vezes realmente acabo usando um construtor padrão ...

Opção 2 Estou bem com.

A opção 3 vai contra o fluxo de Java e C # para o restante da linguagem. Nunca há nada que você "remova" explicitamente, a menos que conte explicitamente tornando as coisas mais privadas do que seriam por padrão em Java.

A opção 4 é horrível - você absolutamente deseja poder forçar a construção com determinados parâmetros. O que new FileStream()significaria?

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

Jon Skeet
fonte
1
Eu gosto da opção 3, porque quando estou escrevendo algo, preciso mais frequentemente ter os dois tipos de construtores do que apenas construtor com parâmetro. Então, eu preferiria uma vez por dia tornar privado um construtor sem parâmetros e depois escrever 10 vezes por dia com construtores sem parâmetros. Mas isso é provavelmente apenas me, estou escrevendo muitas aulas seriaziable ...
Petr Mensik
8
@PetrMensik: Se seu construtor sem parâmetros realmente não precisa fazer absolutamente nada, é uma linha que certamente não precisaria de muito mais código do que uma declaração de "remoção explícita".
quer
2
@PetrMensik: Desculpe, meu erro, sim. Isso é definitivamente o oposto da minha experiência - e eu diria que a inclusão de algo automaticamente é a mais perigosa opção também ... se você acidentalmente acabam não excluí-lo, você pode estragar seus invariantes etc.
Jon Skeet
2
# 4 é o caso de C # structs, para melhor ou para pior.
Jay Bazuzi
4
Eu gosto da opção 1 porque é mais fácil de entender. Nenhum construtor "mágico" que você não pode ver no código. Com a opção 1, o compilador deve emitir um aviso quando (ou talvez não permitir?) Uma classe não estática não possui construtores de instância. Que tal: "Nenhum construtor de instância encontrado para a classe <TYPE>. Você quis declarar a classe estática?"
Jeppe Stig Nielsen
19

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

No início, havia C. C não é orientado a objetos (você pode adotar uma abordagem OO, mas não ajuda ou aplica nada).

Havia C With Classes, que mais tarde foi renomeado para C ++. O C ++ é orientado a objetos e, portanto, incentiva o encapsulamento e garante a invariância de um objeto - na construção e no início e no final de qualquer método, o objeto está em um estado válido.

A coisa natural a fazer com isso é impor que uma classe sempre tenha um construtor para garantir que ele inicie em um estado válido - se o construtor não precisar fazer nada para garantir isso, o construtor vazio documentará esse fato .

Mas um objetivo com C ++ era ser compatível com C, a ponto de, na medida do possível, todos os programas C válidos também serem programas C ++ válidos (não é mais um objetivo ativo, e a evolução de C separada para C ++ significa que ele não é mais válido). )

Um efeito disso foi a duplicação na funcionalidade entre structe class. O primeiro fazendo as coisas da maneira C (tudo público por padrão) e o segundo fazendo as coisas de uma maneira OO (tudo privado por padrão, o desenvolvedor torna público o que quer que seja público).

Outra é que, para um C struct, que não poderia ter um construtor porque C não possui construtores, para ser válido em C ++, era necessário que houvesse um significado para isso na maneira como o C ++ olhava. E assim, embora não ter um construtor fosse contra a prática OO de garantir ativamente um invariável, o C ++ entendeu que isso significava que havia um construtor sem parâmetros padrão que agia como se tivesse um corpo vazio.

Todos os C structsagora eram C ++ válidos structs(o que significava que eram iguais a C ++ classescom tudo - membros e herança - público) tratados externamente como se tivesse um construtor único e sem parâmetros.

Se, no entanto, você colocou um construtor em a classou struct, então estava fazendo as coisas da maneira C ++ / OO em vez da maneira C, e não havia necessidade de um construtor padrão.

Como funcionava como um atalho, as pessoas continuavam usando-o mesmo quando a compatibilidade não era possível de outra maneira (ele usava outros recursos do C ++ que não estavam em C).

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

Stroustrup escreve sobre isso em sua linguagem de programação The C ++ e, mais ainda, com mais foco nos "porquês" da linguagem em The Design and Evolution of C ++ .

=== Resposta original ===

Digamos que isso não aconteceu.

Digamos que eu não queira um construtor sem parâmetros, porque não posso colocar minha classe em um estado significativo sem um. Na verdade, isso é algo que pode acontecer com o structC # (mas se você não pode fazer uso significativo de todos os zeros e nulos structno C #, na melhor das hipóteses, você usa uma otimização não visível ao público e, caso contrário, possui uma falha de design no uso struct).

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

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

Em suma, é melhor não pensar em adicionar um construtor como removendo o padrão, melhor pensar em não ter nenhum construtor como açúcar sintático para adicionar um construtor sem parâmetros que não faz nada.

Jon Hanna
fonte
1
E mais uma vez, vejo que alguém acha que essa é uma resposta ruim o suficiente para rejeitá-la, mas não se incomodou em me esclarecer ou a qualquer outra pessoa. O que pode ser útil.
Jon Hanna
O mesmo na minha resposta. Bem, eu não vejo nada errado aqui, então +1 de mim.
Botz3000 8/08/12
@ Botz3000 Não me importo com o placar, mas se eles têm uma crítica, prefiro lê-lo. Ainda assim, isso me fez pensar em algo a acrescentar ao que foi dito acima.
9118 Jon Hanna
1
E novamente com o voto negativo, sem qualquer explicação. Por favor, se estou sentindo falta de algo tão óbvio que não exija explicação, assuma que sou burro e faça o favor de explicar de qualquer maneira.
Jon Hanna
1
@jogojapan Eu também não sou especialista, mas o cara que é - por ser o cara que tomou a decisão - escreveu sobre isso, então eu não preciso ser. Aliás, é um livro muito interessante; pouco em tecnologia de baixo nível e muito em decisões de design, com partes divertidas, como o discurso de uma pessoa, dizendo que você deveria doar um rim antes de propor um novo recurso (você pensaria muito e só o faria duas vezes) antes do discurso introdução de modelos e exceções.
Jon Hanna
13

O construtor padrão, sem parâmetros, é adicionado se você não fizer nada para assumir o controle sobre a criação de objetos. 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 uma maneira explícita de desabilitar o construtor padrão se desejar que os objetos sejam construtíveis por meio de um construtor com parâmetros.

Anders Abel
fonte
Você realmente tem essa opção. Tornando o construtor sem parâmetros privado.
Mk_21 3/08
5
Isso não é o mesmo. Qualquer método da classe, incluindo métodos estáticos, poderia chamá-lo. Eu prefiro tê-lo totalmente inexistente.
Anders Abel
3

É uma função 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 querer permitir um construtor sem parâmetros é muito maior.

Este é o caso de muitos objetos 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 classe que deseja restringir.

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

mw_21
fonte
3

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

Um construtor é obrigatório para classes 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 classe não seria válida sem um construtor. Portanto, nada de errado em gerar implicitamente um construtor que não faz nada. Certamente parece mais limpo do que ter construtores vazios por toda parte.

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

Botz3000
fonte
1

O construtor padrão pode ser construído apenas quando a classe não tiver um construtor. Os compiladores são escritos de maneira a fornecer isso apenas como um mecanismo de backup.

Se você tiver um construtor parametrizado, talvez não deseje que um objeto seja criado usando o construtor padrão. Se o compilador fornecesse um construtor padrão, você teria que escrever um construtor no-arg e torná-lo privado para impedir que objetos fossem criados usando nenhum argumento.

Além disso, haveria maiores chances de você esquecer de desabilitar ou privatizar o construtor padrão, causando um erro funcional potencial difícil de detectar.

E agora você precisa definir explicitamente um construtor no-arg se quiser que um objeto seja criado da maneira padrão ou passando parâmetros. Isso é fortemente verificado e o compilador reclama de outra maneira, garantindo assim nenhuma brecha aqui.

cinzas
fonte
1

Premissa

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

Maneiras de remover o construtor padrão

Daqui resulta 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 a indicar ao compilador para remover o construtor sem parâmetros (estranho o suficiente para ser 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 se encaixar. Primeiro, ele deve ser substituído por um construtor por parâmetros ou por um construtor não público. Em segundo lugar, os cenários nos quais você não deseja um construtor sem parâmetros são:

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

Conclusão

Aqui temos - exatamente as duas maneiras pelas quais C #, C ++ e Java permitem a remoção do construtor público sem parâmetros padrão.

Zaid Masud
fonte
Claramente estruturado e fácil de seguir, +1. Mas em relação ao (3.) acima: não acho que uma palavra-chave especial para remoção de construtores seja uma idéia tão embaraçosa e, de fato, o C ++ 11 foi introduzido = deletepara esse fim.
jogojapan
1

Eu acho que isso é tratado pelo compilador. Se você abrir a .netmontagem 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á visto.

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

PQubeTechnologies
fonte
0

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

Vivek Pradhan
fonte
0

Uma classe precisa de um construtor. É requisito obrigatório.

  • Se você não criar um, o construtor sem parâmetros será fornecido automaticamente.
  • Se você não quiser um construtor sem parâmetros, precisará criar seu próprio.
  • Se você precisar de ambos, construtor sem parâmetros e construtor com base em parâmetros, poderá adicioná-los manualmente.

Vou responder a sua pergunta com outra, por que queremos sempre um construtor sem parâmetros padrão? há casos em que isso não é desejado; portanto, o desenvolvedor tem o controle de adicionar ou remover conforme necessário.

Jaider
fonte