Perché il costruttore senza parametri predefinito scompare quando ne crei uno con parametri


161

In C #, C ++ e Java, quando si crea un costruttore che accetta parametri, quello senza parametri predefinito scompare. Ho sempre accettato questo fatto, ma ora ho iniziato a chiedermi perché.

Qual è la ragione di questo comportamento? È solo una "misura / ipotesi di sicurezza" che dice "Se hai creato un costruttore tutto tuo, probabilmente non vuoi che questo implicito rimanga in giro"? Oppure ha una ragione tecnica che rende impossibile per il compilatore aggiungerne una dopo aver creato un costruttore?


7
È possibile aggiungere C ++ all'elenco delle lingue che presentano questo comportamento.
Henk Holterman,

18
E in C ++ ora puoi dire Foo() = default;di ripristinare quello predefinito.
Saluti

1
Se il tuo costruttore con parametri può avere argomenti predefiniti per tutti i suoi parametri, allora sarebbe in conflitto con il costruttore senza parametri incorporato, quindi la necessità di eliminarlo quando crei il tuo.
Morwenn,

3
Immagina di essere presente per questo dibattito tra i fondatori del primo compilatore per rendere il requisito predefinito del costruttore e la discussione accesa che avrebbe ispirato.
Kingdango,

7
@HenkHolterman C ++ non è solo un altro con questo comportamento, ma l'originatore, ed è per consentire la compatibilità C, come discusso da Stroustrup in The Design and Evolution of C ++ e come riassunto nella mia risposta aggiornata.
Jon Hanna,

Risposte:


219

Non c'è ragione per cui il compilatore non possa aggiungere il costruttore se ne hai aggiunto uno tuo: il compilatore potrebbe fare praticamente tutto ciò che vuole! Tuttavia, devi guardare a ciò che ha più senso:

  • Se non ho definito alcun costruttore per una classe non statica, molto probabilmente voglio essere in grado di creare un'istanza di quella classe. Per consentire ciò, il compilatore deve aggiungere un costruttore senza parametri, che non avrà alcun effetto se non quello di consentire l'istanza. Questo significa che non devo includere un costruttore vuoto nel mio codice solo per farlo funzionare.
  • Se ho definito un mio costruttore, in particolare uno con parametri, molto probabilmente ho una mia logica che deve essere eseguita durante la creazione della classe. Se il compilatore dovesse creare un costruttore vuoto e senza parametri in questo caso, consentirebbe a qualcuno di saltare la logica che avevo scritto, il che potrebbe portare alla rottura del mio codice in tutti i modi. Se in questo caso desidero un costruttore vuoto predefinito, devo dirlo esplicitamente.

Quindi, in ogni caso, puoi vedere che il comportamento degli attuali compilatori ha più senso in termini di preservare il probabile intento del codice.


2
Penso che il resto della tua risposta dimostri praticamente che la tua prima frase è sbagliata.
Konrad Rudolph,

76
@KonradRudolph, la prima frase dice che un compilatore potrebbe aggiungere un costruttore in questo scenario - il resto della risposta spiega perché non lo fa (per le lingue specificate nella domanda)
JohnL

1
Beh no. Se progettassimo un linguaggio OO da zero, il significato più ovvio di non esistere un costruttore è "hai trascurato di aggiungere un costruttore che garantisca la classe" invariante "e genererebbe un errore di compilazione.
Jon Hanna,

70

Non c'è certamente alcun motivo tecnico per cui la lingua debba essere progettata in questo modo.

Ci sono quattro opzioni in qualche modo realistiche che posso vedere:

  1. Nessun costruttore predefinito
  2. Lo scenario attuale
  3. Fornendo sempre un costruttore predefinito per impostazione predefinita, ma consentendo di eliminarlo esplicitamente
  4. Fornire sempre un costruttore predefinito senza consentirne la soppressione

L'opzione 1 è in qualche modo attraente, in quanto più codice scrivo meno spesso voglio davvero un costruttore senza parametri. Un giorno dovrei contare quanto spesso finisco per usare un costruttore predefinito ...

Opzione 2 con cui sto bene.

L'opzione 3 va contro il flusso di Java e C #, per il resto della lingua. Non c'è mai nulla che tu "rimuova" esplicitamente, a meno che non conti esplicitamente rendendo le cose più private di quanto non lo sarebbero di default in Java.

L'opzione 4 è orribile: vuoi assolutamente essere in grado di forzare la costruzione con determinati parametri. Cosa new FileStream()significherebbe anche?

Quindi, fondamentalmente, se accetti la premessa che fornire un costruttore predefinito ha senso, credo che abbia molto senso sopprimerlo non appena fornisci il tuo costruttore.


1
Mi piace l'opzione 3, perché quando scrivo qualcosa, ho bisogno più spesso di avere entrambi i tipi di costruttori, quindi solo costruttore con parametro. Quindi preferirei una volta al giorno rendere privato qualche costruttore senza parametri, quindi scrivere 10 volte al giorno costruttori senza parametri. Ma probabilmente sono solo io, sto scrivendo molte lezioni seriaziabili ...
Petr Mensik

8
@PetrMensik: se il tuo costruttore senza parametri non deve fare assolutamente nulla, è un one-liner che sicuramente non prenderebbe molto più codice di un'istruzione di "rimozione esplicita".
Jon Skeet,

2
@PetrMensik: scusa, errore mio, sì. Questo è decisamente l'opposto della mia esperienza - e direi che includere qualcosa automaticamente è anche l' opzione più pericolosa ... se finisci per caso non escludendolo, puoi rovinare i tuoi invarianti ecc.
Jon Skeet

2
# 4 è il caso di C # structs, nel bene e nel male.
Jay Bazuzi,

4
Mi piace l'opzione 1 perché è più facile da capire. Nessun costruttore "magico" che non puoi vedere nel codice. Con l'opzione 1 il compilatore dovrebbe emettere un avviso quando (o forse non lo consente?) Una classe non statica non ha costruttori di istanze. Che ne dici di: "Nessun costruttore di istanze trovato per la classe <TYPE>. Intendi dichiarare la classe statica?"
Jeppe Stig Nielsen il

19

Modificare. In realtà, mentre quello che dico nella mia prima risposta è valido, questo è il vero motivo .:

All'inizio c'era C. C non orientato agli oggetti (puoi adottare un approccio OO, ma non ti aiuta né impone nulla).

Poi c'era C With Classes, che in seguito fu ribattezzato C ++. Il C ++ è orientato agli oggetti e quindi incoraggia l'incapsulamento e garantisce l'invarianza di un oggetto: al momento della costruzione e all'inizio e alla fine di qualsiasi metodo, l'oggetto si trova in uno stato valido.

La cosa naturale a che fare con questo, è far rispettare che una classe deve sempre avere un costruttore per assicurarsi che inizi in uno stato valido - se il costruttore non deve fare nulla per garantire ciò, allora il costruttore vuoto documenterà questo fatto .

Ma un obiettivo con C ++ era di essere compatibile con C al punto che, per quanto possibile, tutti i programmi C validi erano anche programmi C ++ validi (non più un obiettivo attivo e l'evoluzione di C separata da C ++ significa che non vale più ).

Un effetto di questo è stata la duplicazione delle funzionalità tra structe class. Il primo fa le cose in modo C (tutto pubblico per impostazione predefinita) e il secondo fa le cose in modo OO (tutto è privato per impostazione predefinita, lo sviluppatore rende attivamente pubblico ciò che desidera).

Un altro è che per un C struct, che non può avere un costruttore perché C non ha costruttori, sia valido in C ++, allora deve esserci un significato per questo nel modo di guardarlo in C ++. E così, pur non avendo un costruttore sarebbe contrario alla pratica OO di assicurare attivamente un invariante, C ++ ha preso questo per significare che c'era un costruttore senza parametri predefinito che si comportava come se avesse un corpo vuoto.

Tutti i C structserano ora C ++ validi structs(il che significava che erano gli stessi del C ++ classescon tutto - membri ed eredità - pubblici) trattati dall'esterno come se avesse un unico costruttore senza parametri.

Se tuttavia hai inserito un costruttore in un classo struct, allora stai facendo le cose nel modo C ++ / OO piuttosto che nel modo C, e non c'era bisogno di un costruttore predefinito.

Dal momento che fungeva da scorciatoia, le persone continuavano a usarlo anche quando la compatibilità non era possibile altrimenti (usava altre funzionalità C ++ non in C).

Quindi quando è arrivato Java (basato su C ++ in molti modi) e successivamente C # (basato su C ++ e Java in diversi modi), hanno mantenuto questo approccio come qualcosa a cui i programmatori potrebbero già essere abituati.

Stroustrup ne parla nel suo linguaggio di programmazione The C ++ e ancora di più, con una maggiore attenzione ai "perché" del linguaggio in The Design and Evolution of C ++ .

=== Risposta originale ===

Diciamo che non è successo.

Diciamo che non voglio un costruttore senza parametri, perché non posso mettere la mia classe in uno stato significativo senza uno. In effetti, questo è qualcosa che può accadere con structC # (ma se non puoi fare un uso significativo di tutti gli zeri e null structin C #, nella migliore delle ipotesi stai usando un'ottimizzazione non pubblicamente visibile, e altrimenti hai un difetto di progettazione nell'uso struct).

Per rendere la mia classe in grado di proteggere i suoi invarianti, ho bisogno di una removeDefaultConstructorparola chiave speciale . Per lo meno, avrei bisogno di creare un costruttore privato senza parametri per assicurarmi che nessun codice chiamante chiami il default.

Il che complica ancora un po 'la lingua. Meglio non farlo.

In tutto, è meglio non pensare di aggiungere un costruttore come rimozione del default, meglio pensare di non avere alcun costruttore come zucchero sintattico per aggiungere un costruttore senza parametri che non fa nulla.


1
E ancora una volta, vedo che qualcuno ritiene che questa sia una risposta abbastanza negativa per votarla, ma non può essere disturbata a illuminare me o chiunque altro. Quale potrebbe essere utile.
Jon Hanna,

Lo stesso sulla mia risposta. Beh, non vedo nulla di sbagliato qui, quindi +1 da me.
Botz3000,

@ Botz3000 Non mi interessa il punteggio, ma se hanno una critica, preferirei leggerlo. Tuttavia, mi ha fatto pensare a qualcosa da aggiungere a quanto sopra.
Jon Hanna,

1
E ancora con il voto negativo senza alcuna spiegazione. Per favore, se mi manca qualcosa di così ovvio da non richiedere spiegazioni, supponi solo che io sia stupido e fammi il favore di spiegarlo comunque.
Jon Hanna,

1
@jogojapan Neanche io sono esperto, ma il ragazzo che è - nell'essere il ragazzo che ha preso la decisione - ne ha scritto, quindi non devo esserlo. Per inciso, è un libro molto interessante; poco sulla tecnologia di basso livello e molto sulle decisioni di progettazione, con parti divertenti come il discorso di una persona che dice che dovresti donare un rene prima di proporre una nuova funzionalità (penseresti molto duramente e lo faresti solo due volte) proprio prima del suo discorso introducendo sia modelli, sia eccezioni.
Jon Hanna,

13

Il costruttore predefinito senza parametri viene aggiunto se non fai nulla tu stesso per assumere il controllo sulla creazione di oggetti. Dopo aver creato un singolo costruttore per assumere il controllo, il compilatore si "arresta" e ti consente di avere il controllo completo.

Se non fosse così, avresti bisogno di un modo esplicito di disabilitare il costruttore predefinito se vuoi solo che gli oggetti siano costruibili attraverso un costruttore con parametri.


In realtà hai questa opzione. Rendere privato il costruttore senza parametri.
mw_21,

5
Non è lo stesso. Qualsiasi metodo della classe, compresi i metodi statici, potrebbe chiamarlo. Preferisco averlo totalmente inesistente.
Anders Abel,

3

È una funzione comoda del compilatore. Se si definisce un costruttore con parametri ma non si definisce un costruttore senza parametri, la possibilità che non si desidera consentire un costruttore senza parametri è molto più elevata.

Questo è il caso di molti oggetti che non hanno senso inizializzare con un costruttore vuoto.

Altrimenti dovresti dichiarare un costruttore privato senza parametri per ogni classe che vuoi limitare.

Secondo me non è un buon stile consentire un costruttore senza parametri per una classe che ha bisogno di parametri per funzionare.


3

Penso che la domanda dovrebbe essere al contrario: perché non è necessario dichiarare un costruttore predefinito se non si sono definiti altri costruttori?

Un costruttore è obbligatorio per le classi non statiche.
Quindi penso che se non hai definito alcun costruttore, il costruttore predefinito generato è solo una comoda funzionalità del compilatore C #, inoltre la tua classe non sarebbe valida senza un costruttore. Quindi niente di male nel generare implicitamente un costruttore che non fa nulla. Sembra certamente più pulito di avere costruttori vuoti tutt'intorno.

Se hai già definito un costruttore, la tua classe è valida, quindi perché il compilatore dovrebbe presumere che desideri un costruttore predefinito? E se non lo vuoi? Implementare un attributo per dire al compilatore di non generare quel costruttore predefinito? Non penso che sarebbe una buona idea.


1

Il costruttore predefinito può essere costruito solo quando la classe non ha un costruttore. I compilatori sono scritti in modo tale da fornire questo solo come meccanismo di backup.

Se si dispone di un costruttore con parametri, è possibile che non si desideri creare un oggetto utilizzando il costruttore predefinito. Se il compilatore avesse fornito un costruttore predefinito, avresti dovuto scrivere un costruttore no-arg e renderlo privato per impedire la creazione di oggetti senza argomenti.

Inoltre, ci sarebbero maggiori possibilità che ti dimentichi di disabilitare o "privatizzi" il costruttore predefinito, causando così un potenziale errore funzionale difficile da rilevare.

E ora devi definire esplicitamente un costruttore no-arg se desideri che un oggetto venga creato nel modo predefinito o passando parametri. Questo è fortemente verificato, e il compilatore si lamenta altrimenti, garantendo così nessuna scappatoia qui.


1

Premessa

Questo comportamento può essere visto come un'estensione naturale della decisione per le classi di avere un costruttore pubblico senza parametri predefinito . Sulla base della domanda che è stata posta, prendiamo questa decisione come premessa e presumiamo che in questo caso non la stiamo mettendo in discussione.

Modi per rimuovere il costruttore predefinito

Ne consegue che deve esserci un modo per rimuovere il costruttore senza parametri pubblico predefinito. Questa rimozione potrebbe essere realizzata nei seguenti modi:

  1. Dichiarare un costruttore senza parametri non pubblico
  2. Rimuove automaticamente il costruttore senza parametri quando viene dichiarato un costruttore con parametri
  3. Qualche parola chiave / attributo da indicare al compilatore per rimuovere il costruttore senza parametri (abbastanza scomodo da escludere facilmente)

Selezione della soluzione migliore

Ora ci chiediamo: se non esiste un costruttore senza parametri, a cosa deve essere sostituito? e In quali tipi di scenari vorremmo rimuovere il costruttore senza parametri pubblico predefinito?

Le cose iniziano a sistemarsi. In primo luogo, deve essere sostituito con un costruttore con parametri o con un costruttore non pubblico. In secondo luogo, gli scenari in cui non si desidera un costruttore senza parametri sono:

  1. Non vogliamo che la classe sia istanziata affatto, o vogliamo controllare la visibilità del costruttore: dichiarare un costruttore non pubblico
  2. Vogliamo forzare i parametri da fornire sulla costruzione: dichiarare un costruttore con parametri

Conclusione

Ecco qui - esattamente i due modi in cui C #, C ++ e Java consentono la rimozione del costruttore pubblico senza parametri predefinito.


Chiaramente strutturato e facile da seguire, +1. Ma per quanto riguarda (3.) sopra: non credo che una parola chiave speciale per la rimozione del costruttore sia un'idea così imbarazzante, e in effetti C ++ 11 ha introdotto = deleteper questo scopo.
jogojapan,

1

Penso che questo sia gestito dal compilatore. Se si apre l' .netassembly in ILDASM, verrà visualizzato il costruttore predefinito, anche se non è nel codice. Se si definisce un costruttore con parametri, il costruttore predefinito non verrà visualizzato.

In realtà quando definisci la classe (non statica), il compilatore fornisce questa funzionalità pensando che stai solo creando un'istanza. E se vuoi eseguire un'operazione specifica, avrai sicuramente il tuo costruttore.


0

È perché quando non si definisce un costruttore il compilatore genera automaticamente un costruttore per te che non accetta alcun argomento. Quando vuoi qualcosa di più da un costruttore, lo superi. Questa NON è una funzione di sovraccarico. Quindi l'unico costruttore che il compilatore vede ora è il tuo costruttore che accetta un argomento. Per contrastare questo problema è possibile passare un valore predefinito se il costruttore viene passato senza valore.


0

Una classe ha bisogno di un costruttore. È un requisito obbligatorio

  • Se non ne crei uno, ti verrà dato automaticamente un costruttore senza parametri.
  • Se non desideri un costruttore senza parametri, devi crearne uno tuo.
  • Se hai bisogno di entrambi, costruttore senza parametri e costruttore basato sui parametri, puoi aggiungerli manualmente.

Risponderò alla tua domanda con un altro, perché vogliamo sempre un costruttore senza parametri predefinito? ci sono casi in cui ciò non è desiderato, quindi lo sviluppatore ha il controllo per aggiungerlo o rimuoverlo secondo necessità.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.