Warum verschwindet der standardmäßige parameterlose Konstruktor, wenn Sie einen mit Parametern erstellen?

162

Wenn Sie in C #, C ++ und Java einen Konstruktor erstellen, der Parameter verwendet, wird der standardmäßige parameterlose Parameter entfernt. Ich habe diese Tatsache immer nur akzeptiert, aber jetzt frage ich mich warum.

Was ist der Grund für dieses Verhalten? Ist es nur eine "Sicherheitsmaßnahme / Vermutung", die besagt: "Wenn Sie einen eigenen Konstruktor erstellt haben, möchten Sie wahrscheinlich nicht, dass dieser implizite herumhängt"? Oder hat es einen technischen Grund, der es dem Compiler unmöglich macht, einen hinzuzufügen, nachdem Sie selbst einen Konstruktor erstellt haben?

Olagjo
quelle
7
Sie können C ++ zur Liste der Sprachen hinzufügen, die dieses Verhalten aufweisen.
Henk Holterman
18
Und in C ++ können Sie jetzt sagen Foo() = default;, dass Sie die Standardeinstellung zurückerhalten möchten.
MSalters
1
Wenn Ihr Konstruktor mit Parametern Standardargumente für alle seine Parameter haben kann, würde dies zu Konflikten mit dem integrierten Konstruktor ohne Parameter führen, weshalb Sie ihn löschen müssen, wenn Sie Ihren eigenen erstellen.
Morwenn
3
Stellen Sie sich vor, Sie wären bei dieser Debatte zwischen den Gründern des ersten Compilers anwesend, der die Standardkonstruktoranforderung gestellt hat, und der hitzigen Diskussion, die er angeregt hätte.
Kingdango
7
@HenkHolterman C ++ ist nicht nur ein anderer mit diesem Verhalten, sondern auch der Urheber, und es soll C-Kompatibilität ermöglichen, wie von Stroustrup in Das Design und die Evolution von C ++ erörtert und in meiner aktualisierten Antwort zusammengefasst.
Jon Hanna

Antworten:

221

Es gibt keinen Grund, warum der Compiler den Konstruktor nicht hinzufügen könnte, wenn Sie Ihren eigenen hinzugefügt hätten - der Compiler könnte so ziemlich alles tun, was er will! Sie müssen sich jedoch ansehen, was am sinnvollsten ist:

  • Wenn ich keinen Konstruktor für eine nicht statische Klasse definiert habe, möchte ich diese Klasse höchstwahrscheinlich instanziieren können. Um das zu ermöglichen, der Compiler muss einen parameterlosen Konstruktor hinzufügen, die keine Wirkung haben, aber Instanziierung zu ermöglichen. Dies bedeutet, dass ich keinen leeren Konstruktor in meinen Code aufnehmen muss, damit er funktioniert.
  • Wenn ich einen eigenen Konstruktor definiert habe, insbesondere einen mit Parametern, habe ich höchstwahrscheinlich eine eigene Logik, die beim Erstellen der Klasse ausgeführt werden muss. Wenn der Compiler in diesem Fall einen leeren, parameterlosen Konstruktor erstellen würde, könnte jemand die von mir geschriebene Logik überspringen , was dazu führen könnte, dass mein Code auf vielfältige Weise beschädigt wird. Wenn ich in diesem Fall einen leeren Standardkonstruktor möchte, muss ich dies explizit sagen.

In jedem Fall können Sie also sehen, dass das Verhalten aktueller Compiler im Hinblick auf die Wahrung der wahrscheinlichen Absicht des Codes am sinnvollsten ist .

Dan Puzey
quelle
2
Ich denke, der Rest Ihrer Antwort beweist ziemlich genau, dass Ihr erster Satz falsch ist.
Konrad Rudolph
76
@KonradRudolph, der erste Satz besagt, dass ein Compiler in diesem Szenario einen Konstruktor hinzufügen könnte - der Rest der Antwort erklärt, warum dies nicht der Fall ist (für die in der Frage angegebenen Sprachen)
JohnL
1
Nun nein. Wenn wir eine OO-Sprache von Grund auf neu entworfen haben, ist die offensichtlichste Bedeutung, dass es keinen Konstruktor gibt, "Sie haben es versäumt, einen Konstruktor hinzuzufügen, der sicherstellt, dass die Klasse" invariant "ist, und dies würde einen Kompilierungsfehler auslösen.
Jon Hanna
71

Es gibt sicherlich keinen technischen Grund , warum die Sprache hat diese Art und Weise zu gestalten.

Es gibt vier etwas realistische Optionen, die ich sehen kann:

  1. Überhaupt keine Standardkonstruktoren
  2. Das aktuelle Szenario
  3. Standardmäßig wird immer ein Standardkonstruktor bereitgestellt, der jedoch explizit unterdrückt werden kann
  4. Immer einen Standardkonstruktor bereitstellen, ohne dass er unterdrückt werden kann

Option 1 ist insofern etwas attraktiv, als je mehr ich codiere, desto seltener möchte ich wirklich einen parameterlosen Konstruktor. Eines Tages sollte ich zählen, wie oft ich tatsächlich einen Standardkonstruktor verwende ...

Option 2 Mir geht es gut.

Option 3 widerspricht für den Rest der Sprache dem Fluss von Java und C #. Es gibt nie etwas, das Sie explizit "entfernen", es sei denn, Sie zählen, um Dinge explizit privater zu machen, als dies in Java standardmäßig der Fall wäre.

Option 4 ist schrecklich - Sie möchten unbedingt die Konstruktion mit bestimmten Parametern erzwingen können. Was würde das new FileStream()überhaupt bedeuten?

Also im Grunde, wenn Sie die Prämisse akzeptieren , dass ein Standard - Konstruktor Bereitstellung Sinn macht, glaube ich es sehr viel Sinn macht es zu unterdrücken, sobald Sie Ihren eigenen Konstruktor zur Verfügung stellen.

Jon Skeet
quelle
1
Ich mag Option 3, weil ich beim Schreiben häufiger beide Konstruktortypen haben muss als nur einen Konstruktor mit Parameter. Also würde ich lieber einmal am Tag einen parameterlosen Konstruktor privat machen und dann 10 mal am Tag parameterlose Konstruktoren schreiben. Aber das bin wahrscheinlich nur ich, ich schreibe viele seriaziable Klassen ...
Petr Mensik
8
@PetrMensik: Wenn Ihr parameterloser Konstruktor wirklich absolut nichts tun muss, ist es ein Einzeiler, der sicherlich nicht viel mehr Code als eine Anweisung zum "expliziten Entfernen" benötigt.
Jon Skeet
2
@PetrMensik: Entschuldigung, mein Fehler, ja. Das ist definitiv das Gegenteil von meiner Erfahrung - und ich würde argumentieren, dass das automatische Einschließen von etwas auch die gefährlichere Option ist ... Wenn Sie es versehentlich nicht ausschließen, können Sie Ihre Invarianten usw.
vermasseln
2
# 4 ist der Fall bei C # structs, zum Guten oder Schlechten.
Jay Bazuzi
4
Ich mag Option 1, weil es leichter zu verstehen ist. Kein "magischer" Konstruktor, den Sie im Code nicht sehen können. Mit Option 1 sollte der Compiler eine Warnung ausgeben, wenn (oder vielleicht sogar nicht erlaubt?) Eine nicht statische Klasse keine Instanzkonstruktoren hat. Wie wäre es mit: "Keine Instanzkonstruktoren für Klasse <TYPE> gefunden. Wollten Sie die Klasse statisch deklarieren?"
Jeppe Stig Nielsen
19

Bearbeiten. Während das, was ich in meiner ersten Antwort sage, gültig ist, ist dies der wahre Grund:

Am Anfang war C. C ist nicht objektorientiert (Sie können einen OO-Ansatz wählen, aber es hilft Ihnen nicht oder erzwingt nichts).

Dann gab es C With Classes, das später in C ++ umbenannt wurde. C ++ ist objektorientiert und fördert daher die Kapselung und stellt die Invariante eines Objekts sicher. Bei der Erstellung sowie zu Beginn und am Ende einer Methode befindet sich das Objekt in einem gültigen Zustand.

Damit können Sie natürlich erzwingen, dass eine Klasse immer einen Konstruktor haben muss, um sicherzustellen, dass sie in einem gültigen Zustand startet. Wenn der Konstruktor nichts tun muss, um dies sicherzustellen, dokumentiert der leere Konstruktor diese Tatsache .

Ein Ziel mit C ++ war es jedoch, mit C so kompatibel zu sein, dass so weit wie möglich alle gültigen C-Programme auch gültige C ++ - Programme waren (kein so aktives Ziel mehr, und die Entwicklung von C getrennt von C ++ bedeutet, dass es nicht mehr gilt ).

Ein Effekt davon war die Verdoppelung der Funktionalität zwischen structund class. Die ersteren machen die Dinge auf die C-Art (standardmäßig alles öffentlich) und die letzteren machen die Dinge auf eine gute OO-Art (standardmäßig alles privat, der Entwickler macht aktiv öffentlich, was sie öffentlich wollen).

Eine andere ist, dass, damit ein C struct, das keinen Konstruktor haben konnte, weil C keine Konstruktoren hat, in C ++ gültig sein musste, dies eine Bedeutung für die C ++ - Sichtweise haben musste. Obwohl es keinen Konstruktor gibt, der gegen die OO-Praxis verstößt, eine Invariante aktiv sicherzustellen, bedeutet C ++, dass es einen standardmäßigen parameterlosen Konstruktor gibt, der sich so verhält, als hätte er einen leeren Körper.

Alle C structswaren jetzt gültige C ++ structs(was bedeutete, dass sie mit C ++ identisch waren, classeswobei alles - Mitglieder und Vererbung - öffentlich) von außen so behandelt wurde, als hätte es einen einzigen, parameterlosen Konstruktor.

Wenn Sie jedoch einen Konstruktor in ein classoder structeingefügt haben, haben Sie die Dinge eher auf C ++ / OO-Weise als auf C-Weise ausgeführt, und es war kein Standardkonstruktor erforderlich.

Da es als Abkürzung diente, wurde es weiterhin verwendet, auch wenn die Kompatibilität sonst nicht möglich war (es wurden andere C ++ - Funktionen verwendet, die nicht in C enthalten waren).

Als Java (in vielerlei Hinsicht auf C ++ basierend) und später C # (auf unterschiedliche Weise auf C ++ und Java basierend) auf den Markt kamen, behielten sie diesen Ansatz bei, wie es Codierer möglicherweise bereits gewohnt sind.

Stroustrup schreibt darüber in seiner Programmiersprache The C ++ und noch mehr, wobei er sich mehr auf das "Warum" der Sprache in The Design and Evolution of C ++ konzentriert .

=== Ursprüngliche Antwort ===

Nehmen wir an, das ist nicht passiert.

Angenommen, ich möchte keinen parameterlosen Konstruktor, weil ich meine Klasse ohne einen nicht in einen sinnvollen Zustand versetzen kann. In der Tat kann dies structin C # passieren (aber wenn Sie in C # keine Nullen und Nullen sinnvoll verwenden können, verwenden structSie bestenfalls eine nicht öffentlich sichtbare Optimierung und haben ansonsten eine Konstruktionsfehler bei der Verwendung struct).

Damit meine Klasse ihre Invarianten schützen kann, benötige ich ein spezielles removeDefaultConstructorSchlüsselwort. Zumindest müsste ich einen privaten parameterlosen Konstruktor erstellen, um sicherzustellen, dass kein aufrufender Code die Standardeinstellung aufruft.

Was die Sprache noch komplizierter macht. Besser nicht.

Insgesamt ist es am besten, nicht daran zu denken, einen Konstruktor hinzuzufügen, um den Standard zu entfernen, sondern sich überhaupt keinen Konstruktor als syntaktischen Zucker für das Hinzufügen eines parameterlosen Konstruktors vorzustellen, der nichts tut.

Jon Hanna
quelle
1
Und wieder sehe ich, dass jemand der Meinung ist, dass dies eine Antwort ist, die schlecht genug ist, um sie abzustimmen, aber er oder andere könnten nicht die Mühe machen, sie aufzuklären. Was vielleicht nützlich sein könnte.
Jon Hanna
Gleiches gilt für meine Antwort. Nun, ich sehe hier nichts falsch, also +1 von mir.
Botz3000
@ Botz3000 Die Partitur interessiert mich nicht, aber wenn sie eine Kritik haben, würde ich sie lieber lesen. Trotzdem fiel mir etwas ein, das ich dem oben Gesagten hinzufügen konnte.
Jon Hanna
1
Und wieder mit dem Down-Voting ohne jede Erklärung. Wenn mir etwas so Offensichtliches fehlt, dass es keiner Erklärung bedarf, nehmen Sie einfach an, dass ich dumm bin, und tun Sie mir den Gefallen, es trotzdem zu erklären.
Jon Hanna
1
@jogojapan Ich bin auch kein Experte, aber der Typ, der - als derjenige, der die Entscheidung getroffen hat - darüber geschrieben hat, also muss ich es nicht sein. Übrigens ist es ein sehr interessantes Buch; Wenig über Low-Level-Technologie und viel über Designentscheidungen. Amüsante Dinge wie die Rede einer Person besagen, dass Sie eine Niere spenden müssen, bevor Sie eine neue Funktion vorschlagen (Sie würden sehr viel nachdenken und es nur zweimal tun), kurz vor seiner Rede Einführung sowohl von Vorlagen als auch von Ausnahmen.
Jon Hanna
13

Der standardmäßige, parameterlose Konstruktor wird hinzugefügt, wenn Sie selbst nichts tun, um die Kontrolle über die Objekterstellung zu übernehmen. Sobald Sie einen einzelnen Konstruktor erstellt haben, um die Kontrolle zu übernehmen, "zieht" sich der Compiler zurück und gibt Ihnen die volle Kontrolle.

Wenn dies nicht so wäre, müssten Sie den Standardkonstruktor explizit deaktivieren, wenn Objekte nur über einen Konstruktor mit Parametern konstruierbar sein sollen.

Anders Abel
quelle
Sie haben tatsächlich diese Option. Den parameterlosen Konstruktor privat machen.
mw_21
5
Das ist nicht dasselbe Jede Methode der Klasse, einschließlich statischer Methoden, kann sie aufrufen. Ich bevorzuge es, wenn es überhaupt nicht existiert.
Anders Abel
3

Es ist eine Komfortfunktion des Compilers. Wenn Sie einen Konstruktor mit Parametern definieren, aber keinen parameterlosen Konstruktor definieren, ist die Wahrscheinlichkeit, dass Sie keinen parameterlosen Konstruktor zulassen möchten, viel höher.

Dies ist bei vielen Objekten der Fall, deren Initialisierung mit einem leeren Konstruktor einfach keinen Sinn macht.

Andernfalls müssten Sie für jede Klasse, die Sie einschränken möchten, einen privaten parameterlosen Konstruktor deklarieren.

Meiner Meinung nach ist es kein guter Stil, einen parameterlosen Konstruktor für eine Klasse zuzulassen, für deren Funktion Parameter erforderlich sind.

mw_21
quelle
3

Ich denke, die Frage sollte umgekehrt sein: Warum müssen Sie keinen Standardkonstruktor deklarieren, wenn Sie keine anderen Konstruktoren definiert haben?

Ein Konstruktor ist für nicht statische Klassen obligatorisch.
Wenn Sie also keine Konstruktoren definiert haben, ist der generierte Standardkonstruktor nur eine praktische Funktion des C # -Compilers. Ohne einen Konstruktor wäre Ihre Klasse auch nicht gültig. Es ist also nichts Falsches daran, implizit einen Konstruktor zu generieren, der nichts tut. Es sieht auf jeden Fall sauberer aus, als überall leere Konstruktoren zu haben.

Wenn Sie bereits einen Konstruktor definiert haben, ist Ihre Klasse gültig. Warum sollte der Compiler also davon ausgehen, dass Sie einen Standardkonstruktor möchten? Was ist, wenn Sie keine wollen? Ein Attribut implementieren, um den Compiler anzuweisen, diesen Standardkonstruktor nicht zu generieren? Ich denke nicht, dass das eine gute Idee wäre.

Botz3000
quelle
1

Der Standardkonstruktor kann nur erstellt werden, wenn die Klasse keinen Konstruktor hat. Compiler sind so geschrieben, dass sie nur als Sicherungsmechanismus dienen.

Wenn Sie einen parametrisierten Konstruktor haben, möchten Sie möglicherweise nicht, dass ein Objekt mit dem Standardkonstruktor erstellt wird. Hätte der Compiler einen Standardkonstruktor bereitgestellt, hätten Sie einen Konstruktor ohne Argumente schreiben und privat machen müssen, um zu verhindern, dass Objekte ohne Argumente erstellt werden.

Außerdem besteht eine höhere Wahrscheinlichkeit, dass Sie das Deaktivieren oder "Privatisieren" des Standardkonstruktors vergessen und dadurch einen potenziellen Funktionsfehler verursachen, der schwer zu erkennen ist.

Und jetzt müssen Sie explizit einen Konstruktor ohne Argumente definieren, wenn ein Objekt entweder standardmäßig oder durch Übergabe von Parametern erstellt werden soll. Dies wird stark überprüft, und der Compiler beschwert sich anders, wodurch hier keine Lücke sichergestellt wird.

Asche
quelle
1

Prämisse

Dieses Verhalten kann als natürliche Erweiterung der Entscheidung für Klassen angesehen werden, einen standardmäßigen öffentlichen parameterlosen Konstruktor zu haben . Basierend auf der gestellten Frage nehmen wir diese Entscheidung als Voraussetzung und gehen davon aus, dass wir sie in diesem Fall nicht in Frage stellen.

Möglichkeiten zum Entfernen des Standardkonstruktors

Daraus folgt, dass es eine Möglichkeit geben muss, den standardmäßigen öffentlichen parameterlosen Konstruktor zu entfernen. Diese Entfernung kann auf folgende Weise erreicht werden:

  1. Deklarieren Sie einen nicht öffentlichen parameterlosen Konstruktor
  2. Entfernen Sie den parameterlosen Konstruktor automatisch, wenn ein Konstruktor mit Parametern deklariert wird
  3. Ein Schlüsselwort / Attribut, das dem Compiler angezeigt wird, um den parameterlosen Konstruktor zu entfernen (umständlich genug, dass es leicht auszuschließen ist)

Auswahl der besten Lösung

Jetzt fragen wir uns: Wenn es keinen parameterlosen Konstruktor gibt, durch welchen muss er ersetzt werden? und Unter welchen Szenarien möchten wir den standardmäßigen öffentlichen parameterlosen Konstruktor entfernen?

Die Dinge fangen an, sich zusammenzufügen. Erstens muss es entweder durch einen Konstruktor mit Parametern oder durch einen nicht öffentlichen Konstruktor ersetzt werden. Zweitens sind die Szenarien, unter denen Sie keinen parameterlosen Konstruktor benötigen, folgende:

  1. Wir möchten nicht, dass die Klasse überhaupt instanziiert wird, oder wir möchten die Sichtbarkeit des Konstruktors steuern: Deklarieren Sie einen nicht öffentlichen Konstruktor
  2. Wir möchten die Bereitstellung von Parametern für die Konstruktion erzwingen: Deklarieren Sie einen Konstruktor mit Parametern

Fazit

Da haben wir es - genau die zwei Möglichkeiten, mit denen C #, C ++ und Java das Entfernen des standardmäßigen öffentlichen parameterlosen Konstruktors ermöglichen.

Zaid Masud
quelle
Klar strukturiert und leicht zu folgen, +1. Aber zu (3.) oben: Ich denke nicht, dass ein spezielles Schlüsselwort zum Entfernen von Konstruktoren eine so unangenehme Idee ist, und tatsächlich hat C ++ 11 = deletezu diesem Zweck eingeführt.
Jogojapan
1

Ich denke, das wird vom Compiler erledigt. Wenn Sie die .netAssembly in öffnen ILDASM, wird der Standardkonstruktor angezeigt, auch wenn er nicht im Code enthalten ist. Wenn Sie einen parametrisierten Konstruktor definieren, wird der Standardkonstruktor nicht angezeigt.

Wenn Sie die Klasse definieren (nicht statisch), stellt der Compiler diese Funktion bereit und denkt, dass Sie nur eine Instanz erstellen. Und wenn Sie möchten, dass eine bestimmte Operation ausgeführt wird, haben Sie sicherlich einen eigenen Konstruktor.

PQubeTechnologies
quelle
0

Wenn Sie keinen Konstruktor definieren, generiert der Compiler automatisch einen Konstruktor für Sie, der keine Argumente akzeptiert. Wenn Sie etwas mehr von einem Konstruktor wollen, überschreiben Sie es. Dies ist KEINE Funktionsüberlastung. Der einzige Konstruktor, den der Compiler jetzt sieht, ist Ihr Konstruktor, der ein Argument akzeptiert. Um diesem Problem entgegenzuwirken, können Sie einen Standardwert übergeben, wenn der Konstruktor ohne Wert übergeben wird.

Vivek Pradhan
quelle
0

Eine Klasse braucht einen Konstruktor. Es ist obligatorisch.

  • Wenn Sie keinen erstellen, erhalten Sie automatisch einen parameterlosen Konstruktor.
  • Wenn Sie keinen parameterlosen Konstruktor möchten, müssen Sie einen eigenen erstellen.
  • Wenn Sie sowohl einen parameterlosen Konstruktor als auch einen parameterbasierten Konstruktor benötigen, können Sie diese manuell hinzufügen.

Ich werde Ihre Frage mit einem anderen beantworten, warum wollen wir immer einen standardmäßigen parameterlosen Konstruktor? Es gibt Fälle, in denen dies nicht erwünscht ist, sodass der Entwickler die Kontrolle hat, es nach Bedarf hinzuzufügen oder zu entfernen.

Jaider
quelle