Dlaczego domyślny konstruktor bez parametrów znika, gdy tworzysz go z parametrami

161

W C #, C ++ i Javie, kiedy tworzysz konstruktor pobierający parametry, domyślny bez parametrów znika. Zawsze akceptowałem ten fakt, ale teraz zacząłem się zastanawiać, dlaczego.

Jaki jest powód tego zachowania? Czy to tylko „środek bezpieczeństwa / domysł” mówiący „Jeśli stworzyłeś własnego konstruktora, prawdopodobnie nie chcesz, aby ten ukryty kręcił się w pobliżu”? A może ma to techniczny powód, który uniemożliwia kompilatorowi dodanie go po samodzielnym utworzeniu konstruktora?

olagjo
źródło
7
Możesz dodać C ++ do listy języków, które mają to zachowanie.
Henk Holterman
18
W C ++ możesz teraz powiedzieć, że Foo() = default;chcesz przywrócić domyślny.
MSalters
1
Jeśli twój konstruktor z parametrami może mieć domyślne argumenty dla wszystkich swoich parametrów, to powodowałoby to konflikt z wbudowanym konstruktorem bez parametrów, stąd potrzeba usunięcia go podczas tworzenia własnego.
Morwenn
3
Wyobraź sobie, że jesteś obecny podczas tej debaty między twórcami pierwszego kompilatora, który wprowadził wymóg domyślnego konstruktora, a gorącą dyskusją, którą by zainspirował.
kingdango
7
@HenkHolterman C ++ to nie tylko kolejny z tym zachowaniem, ale twórca, który pozwala na kompatybilność z C, jak omówiono przez Stroustrup w The Design and Evolution of C ++ i podsumowałem w mojej zaktualizowanej odpowiedzi.
Jon Hanna

Odpowiedzi:

219

Nie ma powodu, dla którego kompilator nie mógł dodać konstruktora, jeśli dodałeś własny - kompilator mógł zrobić prawie wszystko, co chce! Musisz jednak przyjrzeć się temu, co ma największy sens:

  • Jeśli nie zdefiniowałem żadnego konstruktora dla klasy niestatycznej, najprawdopodobniej chcę mieć możliwość utworzenia instancji tej klasy. Aby to umożliwić, kompilator musi dodać konstruktor bez parametrów, który nie będzie miał żadnego efektu, ale umożliwi tworzenie instancji. Oznacza to, że nie muszę umieszczać w kodzie pustego konstruktora, aby działał.
  • Jeśli zdefiniowałem własny konstruktor, zwłaszcza taki z parametrami, to najprawdopodobniej mam własną logikę, która musi zostać wykonana podczas tworzenia klasy. Gdyby kompilator utworzył w tym przypadku pusty konstruktor bez parametrów, pozwoliłoby to komuś pominąć napisaną przeze mnie logikę, co mogłoby prowadzić do złamania mojego kodu na wiele różnych sposobów. Jeśli chcę w tym przypadku domyślnego pustego konstruktora, muszę to wyraźnie powiedzieć.

Tak więc w każdym przypadku widać, że zachowanie obecnych kompilatorów ma największy sens, jeśli chodzi o zachowanie prawdopodobnej intencji kodu.

Dan Puzey
źródło
2
Myślę, że reszta twojej odpowiedzi w dużym stopniu dowodzi, że twoje pierwsze zdanie jest błędne.
Konrad Rudolph
76
@KonradRudolph, pierwsze zdanie mówi, że kompilator może dodać konstruktora w tym scenariuszu - reszta odpowiedzi wyjaśnia, dlaczego tak się nie dzieje (dla języków określonych w pytaniu)
JohnL
1
Więc nie. Gdybyśmy zaprojektowali język obiektowy od zera, najbardziej oczywistym znaczeniem braku konstruktora jest „zaniedbanie dodania konstruktora zapewniającego niezmienność klasy” i spowodowałoby to błąd kompilacji.
Jon Hanna
70

Z pewnością nie ma technicznego powodu, dla którego język miałby być zaprojektowany w ten sposób.

Widzę cztery nieco realistyczne opcje:

  1. Brak domyślnych konstruktorów
  2. Obecny scenariusz
  3. Zawsze domyślnie dostarcza domyślnego konstruktora, ale pozwala na jawne pomijanie
  4. Zawsze dostarczanie domyślnego konstruktora bez zezwolenia na jego wyłączenie

Wariant 1 jest dość atrakcyjny, że więcej kodu I rzadziej ja naprawdę chcę konstruktora bez parametrów. Niektóre dni mam policzyć, jak często ja rzeczywiście kończy się przy użyciu domyślnego konstruktora ...

Opcja 2 jest w porządku.

Opcja 3 jest sprzeczna z przepływem języka Java i C # w pozostałej części języka. Nigdy nie ma niczego, co wyraźnie „usuwasz”, chyba że policzysz jawnie czyniąc rzeczy bardziej prywatnymi, niż byłoby to domyślnie w Javie.

Opcja 4 jest okropna - absolutnie chcesz mieć możliwość wymuszenia konstrukcji o określonych parametrach. Co by to w new FileStream()ogóle oznaczało?

Więc w zasadzie, jeśli akceptujesz założenie, że dostarczanie domyślnego konstruktora ma w ogóle sens, uważam, że bardzo sensowne jest wyłączenie go, gdy tylko dostarczysz własnego konstruktora.

Jon Skeet
źródło
1
Podoba mi się opcja 3, ponieważ kiedy coś piszę, częściej potrzebuję mieć oba typy konstruktorów niż tylko konstruktor z parametrem. Więc wolałbym raz dziennie ustawić konstruktor bez parametrów jako prywatny, a następnie pisać 10 razy dziennie konstruktory bez parametrów. Ale to chyba tylko ja, piszę dużo możliwych do seriazowalności klas ...
Petr Mensik
8
@PetrMensik: Jeśli twój konstruktor bez parametrów naprawdę nie musi nic robić, jest to jednolinijkowy, który z pewnością nie wymagałby dużo więcej kodu niż instrukcja „jawnego usunięcia”.
Jon Skeet
2
@PetrMensik: Przepraszam, mój błąd, tak. To zdecydowanie przeciwieństwo mojego doświadczenia - i argumentowałbym, że włączanie czegoś automatycznie jest również bardziej niebezpieczną opcją ... jeśli przypadkowo nie wykluczysz tego, możesz spieprzyć swoje niezmienniki itp.
Jon Skeet
2
# 4 jest w przypadku C # struct, na dobre lub na złe.
Jay Bazuzi
4
Podoba mi się opcja 1, ponieważ jest łatwiejsza do zrozumienia. Żadnego „magicznego” konstruktora, którego nie widać w kodzie. Z opcją 1 kompilator powinien emitować ostrzeżenie, gdy (a może nawet zabronić?) Klasa niestatyczna nie ma konstruktorów instancji. Co powiesz na: „Nie znaleziono konstruktorów instancji dla klasy <TYPE>. Czy chodziło Ci o zadeklarowanie klasy jako statycznej?”
Jeppe Stig Nielsen
19

Edytować. Właściwie, chociaż to, co powiem w mojej pierwszej odpowiedzi, jest ważne, to jest prawdziwy powód .:

Na początku było C. C nie jest zorientowane obiektowo (możesz przyjąć podejście obiektowe, ale to ci nie pomaga ani niczego nie wymusza).

Potem było C With Classes, które zostało później przemianowane na C ++. C ++ jest zorientowany obiektowo, dlatego zachęca do hermetyzacji i zapewnienia niezmienności obiektu - po skonstruowaniu oraz na początku i na końcu dowolnej metody obiekt jest w prawidłowym stanie.

Naturalną rzeczą jest wymuszenie, że klasa musi zawsze mieć konstruktora, aby upewnić się, że zaczyna się w poprawnym stanie - jeśli konstruktor nie musi nic robić, aby to zapewnić, pusty konstruktor udokumentuje ten fakt .

Ale celem z C ++ było być kompatybilnym z C do tego stopnia, że ​​w miarę możliwości wszystkie prawidłowe programy w C były również prawidłowymi programami w C ++ (nie jest już tak aktywnym celem, a ewolucja C oddzielnie do C ++ oznacza, że ​​już nie jest ).

Jednym z efektów tego było powielanie funkcjonalności między structa class. Pierwszy robi rzeczy w C (domyślnie wszystko jest publiczne), a drugi robi wszystko w dobry sposób OO (domyślnie wszystko jest prywatne, deweloper aktywnie upublicznia to, czego chce).

Innym jest to, że aby C struct, które nie mogło mieć konstruktora, ponieważ C nie ma konstruktorów, było poprawne w C ++, musi to mieć znaczenie w sposobie patrzenia na to w C ++. I tak, chociaż brak konstruktora byłby sprzeczny z praktyką OO polegającą na aktywnym zapewnianiu niezmiennika, C ++ przyjęło to jako oznaczenie, że istnieje domyślny konstruktor bez parametrów, który działał tak, jakby miał puste ciało.

Wszystkie C structsbyły teraz poprawne w C ++ structs(co oznaczało, że były takie same jak C ++ classesze wszystkim - składowymi i dziedziczeniem - publicznymi) traktowane z zewnątrz tak, jakby miały pojedynczy konstruktor bez parametrów.

Jeśli jednak umieściłeś konstruktor w a classlub struct, to robiłeś rzeczy w sposób C ++ / OO, a nie w C, i nie było potrzeby stosowania domyślnego konstruktora.

Ponieważ służył jako skrót, ludzie używali go nawet wtedy, gdy kompatybilność nie była możliwa w inny sposób (używał innych funkcji C ++ nie w C).

Dlatego kiedy pojawiła się Java (oparta na C ++ na wiele sposobów), a później C # (oparta na C ++ i Javie na różne sposoby), zachowali to podejście, ponieważ programiści mogli już być przyzwyczajeni.

Stroustrup pisze o tym w swoim The C ++ Programming Language, a nawet bardziej, skupiając się bardziej na tym, „dlaczego” języka w The Design and Evolution of C ++ .

=== Oryginalna odpowiedź ===

Powiedzmy, że tak się nie stało.

Powiedzmy, że nie chcę konstruktora bez parametrów, ponieważ bez niego nie mogę wprowadzić mojej klasy w znaczący stan. Rzeczywiście, jest to coś, co może się zdarzyć structw C # (ale jeśli nie możesz sensownie używać samych zer i zer structw C #, w najlepszym razie używasz niepublicznie widocznej optymalizacji, a poza tym masz wada projektowa w użyciu struct).

Aby moja klasa mogła chronić swoje niezmienniki, potrzebuję specjalnego removeDefaultConstructorsłowa kluczowego. Musiałbym przynajmniej utworzyć prywatny konstruktor bez parametrów, aby upewnić się, że żaden kod wywołujący nie wywołuje wartości domyślnej.

Co jeszcze bardziej komplikuje język. Lepiej tego nie robić.

Podsumowując, najlepiej nie myśleć o dodawaniu konstruktora jako usuwaniu wartości domyślnej, lepiej pomyśleć o braku konstruktora jako cukru składniowego do dodawania konstruktora bez parametrów, który nic nie robi.

Jon Hanna
źródło
1
I znowu, widzę, że ktoś uważa, że ​​jest to wystarczająco zła odpowiedź, aby ją przegłosować, ale nie przeszkadzało mi to oświecenie mnie ani nikogo innego. Co może być, wiesz, przydatne.
Jon Hanna
To samo dotyczy mojej odpowiedzi. Cóż, nie widzę tu nic złego, więc daj mi +1.
Botz3000
@ Botz3000 Nie obchodzi mnie wynik, ale jeśli mają krytykę, wolę ją przeczytać. Mimo to pomyślałem o czymś, co mógłbym dodać do powyższego.
Jon Hanna
1
I znowu z głosowaniem w dół bez żadnego wyjaśnienia. Proszę, jeśli brakuje mi czegoś tak oczywistego, że nie wymaga to wyjaśnienia, po prostu załóż, że jestem głupi i zrób mi przysługę, wyjaśniając to mimo wszystko.
Jon Hanna
1
@jogojapan Ja też nie jestem ekspertem, ale facet, który jest facetem, który podjął decyzję, napisał o tym, więc ja nie muszę. Nawiasem mówiąc, to bardzo interesująca książka; niewiele na temat technologii niskiego poziomu i wiele decyzji projektowych, z zabawnymi fragmentami, takimi jak przemówienie jednej osoby, która mówi, że przed zaproponowaniem nowej funkcji należy oddać nerkę (można by się mocno zastanowić i zrobić to tylko dwa razy) tuż przed jego przemówieniem wprowadzenie zarówno szablonów, jak i wyjątków.
Jon Hanna
13

Domyślny konstruktor bez parametrów jest dodawany, jeśli nie robisz niczego, aby przejąć kontrolę nad tworzeniem obiektów. Po utworzeniu pojedynczego konstruktora do przejęcia kontroli, kompilator „wycofuje się” i daje Ci pełną kontrolę.

Gdyby tak nie było, potrzebowałbyś jakiegoś jawnego sposobu wyłączenia domyślnego konstruktora, jeśli chcesz, aby obiekty były konstruowalne tylko za pomocą konstruktora z parametrami.

Anders Abel
źródło
Naprawdę masz taką opcję. Ustawianie konstruktora bez parametrów jako prywatnego.
mw_21
5
To nie to samo. Można ją wywołać dowolną metodą tej klasy, w tym metodami statycznymi. Wolę mieć to zupełnie nieistniejące.
Anders Abel
3

Jest to wygodna funkcja kompilatora. Jeśli zdefiniujesz Konstruktor z parametrami, ale nie zdefiniujesz konstruktora bez parametrów, prawdopodobieństwo, że nie chcesz zezwolić na konstruktor bez parametrów, jest znacznie większe.

Dzieje się tak w przypadku wielu obiektów, których inicjalizacja z pustym konstruktorem po prostu nie ma sensu.

W przeciwnym razie musiałbyś zadeklarować prywatny konstruktor bez parametrów dla każdej klasy, którą chcesz ograniczyć.

Moim zdaniem nie jest dobrym stylem zezwalanie na konstruktor bez parametrów dla klasy, która potrzebuje parametrów do działania.

mw_21
źródło
3

Myślę, że pytanie powinno być odwrotne: dlaczego nie musisz zadeklarować domyślnego konstruktora, jeśli nie zdefiniowałeś żadnych innych konstruktorów?

Konstruktor jest obowiązkowy dla klas niestatycznych.
Więc myślę, że jeśli nie zdefiniowałeś żadnych konstruktorów, wygenerowany domyślny konstruktor jest po prostu wygodną funkcją kompilatora C #, również twoja klasa nie byłaby ważna bez konstruktora. Nie ma więc nic złego w niejawnym generowaniu konstruktora, który nic nie robi. Z pewnością wygląda to lepiej niż posiadanie pustych konstruktorów dookoła.

Jeśli masz już zdefiniowany konstruktor, twoja klasa jest prawidłowa, więc dlaczego kompilator miałby zakładać, że chcesz konstruktora domyślnego? A jeśli nie chcesz? Zaimplementować atrybut, aby poinformować kompilator, aby nie generował tego domyślnego konstruktora? Nie sądzę, żeby to był dobry pomysł.

Botz3000
źródło
1

Konstruktor domyślny można skonstruować tylko wtedy, gdy klasa nie ma konstruktora. Kompilatory są napisane w taki sposób, aby zapewnić to tylko jako mechanizm zapasowy.

Jeśli masz sparametryzowany konstruktor, możesz nie chcieć, aby obiekt był tworzony przy użyciu domyślnego konstruktora. Gdyby kompilator dostarczył domyślnego konstruktora, musiałbyś napisać konstruktor bezargumentowy i uczynić go prywatnym, aby zapobiec tworzeniu obiektów bez argumentów.

Ponadto istnieje większe prawdopodobieństwo, że zapomnisz o wyłączeniu lub „sprywatyzowaniu” domyślnego konstruktora, co spowoduje trudny do wykrycia potencjalny błąd funkcjonalny.

A teraz musisz jawnie zdefiniować konstruktor bezargumentowy, jeśli chcesz, aby obiekt został utworzony w domyślny sposób lub przez przekazanie parametrów. Jest to mocno sprawdzane, a kompilator narzeka inaczej, zapewniając w ten sposób brak luki w tym miejscu.

proch
źródło
1

Przesłanka

To zachowanie można postrzegać jako naturalne rozszerzenie decyzji dla klas, aby miały domyślny publiczny konstruktor bez parametrów . Na podstawie zadanego pytania przyjmujemy tę decyzję jako przesłankę i zakładamy, że nie kwestionujemy jej w tym przypadku.

Sposoby usuwania domyślnego konstruktora

Wynika z tego, że musi istnieć sposób na usunięcie domyślnego publicznego konstruktora bez parametrów. To usunięcie można przeprowadzić na następujące sposoby:

  1. Zadeklaruj niepubliczny konstruktor bez parametrów
  2. Automatycznie usuń konstruktora bez parametrów, gdy zostanie zadeklarowany konstruktor z parametrami
  3. Niektóre słowa kluczowe / atrybuty do wskazania kompilatorowi w celu usunięcia konstruktora bez parametrów (na tyle niezręczne, że można je łatwo wykluczyć)

Wybór najlepszego rozwiązania

Teraz zadajemy sobie pytanie: jeśli nie ma konstruktora bez parametrów, czym należy go zastąpić? iw jakich typach scenariuszy chcielibyśmy usunąć domyślny publiczny konstruktor bez parametrów?

Wszystko zaczyna się układać. Po pierwsze, należy go zastąpić konstruktorem z parametrami lub konstruktorem niepublicznym. Po drugie, scenariusze, w których nie chcesz konstruktora bez parametrów, to:

  1. Nie chcemy w ogóle tworzyć instancji klasy lub chcemy kontrolować widoczność konstruktora: zadeklaruj niepubliczny konstruktor
  2. Chcemy wymusić podawanie parametrów na konstrukcji: zadeklaruj konstruktora z parametrami

Wniosek

Mamy to - dokładnie na dwa sposoby, na które C #, C ++ i Java pozwalają na usunięcie domyślnego publicznego konstruktora bez parametrów.

Zaid Masud
źródło
Jasna struktura i łatwe do naśladowania, +1. Ale jeśli chodzi o (3.) powyżej: nie sądzę, aby specjalne słowo kluczowe do usuwania konstruktora było tak niezręcznym pomysłem, aw rzeczywistości C ++ 11 wprowadził = deletew tym celu.
jogojapan
1

Myślę, że jest to obsługiwane przez kompilator. Jeśli otworzysz .netzestaw w ILDASM, zobaczysz domyślny konstruktor, nawet jeśli nie ma go w kodzie. Jeśli zdefiniujesz sparametryzowany konstruktor, domyślny konstruktor nie będzie widoczny.

W rzeczywistości, kiedy definiujesz klasę (niestatyczną), kompilator zapewnia tę funkcję, myśląc, że będziesz po prostu tworzyć instancję. A jeśli chcesz wykonać jakąś konkretną operację, na pewno będziesz mieć własnego konstruktora.

PQubeTechnologies
źródło
0

Dzieje się tak, ponieważ gdy nie definiujesz konstruktora, kompilator automatycznie generuje dla Ciebie konstruktor, który nie przyjmuje żadnych argumentów. Jeśli chcesz czegoś więcej od konstruktora, przeskakujesz to. To NIE jest przeciążenie funkcji. Zatem jedynym konstruktorem, który teraz widzi kompilator, jest konstruktor, który przyjmuje argument. Aby przeciwdziałać temu problemowi, możesz przekazać wartość domyślną, jeśli konstruktor zostanie przekazany bez wartości.

Vivek Pradhan
źródło
0

Klasa potrzebuje konstruktora. Jest to wymóg obowiązkowy.

  • Jeśli go nie utworzysz, automatycznie otrzymasz konstruktor bez parametrów.
  • Jeśli nie chcesz konstruktora bez parametrów, musisz utworzyć własny.
  • Jeśli potrzebujesz zarówno konstruktora bez parametrów, jak i konstruktora opartego na parametrach, możesz dodać je ręcznie.

Odpowiem na inne pytanie, dlaczego zawsze chcemy mieć domyślny konstruktor bez parametrów? są przypadki, w których nie jest to pożądane, więc programista ma kontrolę nad dodaniem lub usunięciem go w razie potrzeby.

Jaider
źródło