Dlaczego C ++ nie ma śmietnika?


270

Nie zadaję tego pytania przede wszystkim ze względu na zalety zbierania śmieci. Moim głównym powodem, dla którego o to pytam, jest to, że wiem, że Bjarne Stroustrup powiedział, że C ++ będzie miało w pewnym momencie moduł wyrzucający śmieci.

To powiedziawszy, dlaczego nie zostało dodane? Istnieje już kilka śmieciarek dla C ++. Czy to tylko jedna z tych rzeczy typu „łatwiej powiedzieć niż zrobić”? Czy są też inne powody, dla których nie został dodany (i nie zostanie dodany w C ++ 11)?

Linki krzyżowe:

Żeby wyjaśnić, rozumiem powody, dla których C ++ nie miał śmieciarza, kiedy został stworzony. Zastanawiam się, dlaczego kolektora nie można dodać.


26
Jest to jeden z dziesięciu mitów na temat C ++, które zawsze przywołują hejterzy. Odśmiecanie nie jest „wbudowane”, ale istnieje kilka prostych sposobów, aby to zrobić C ++. Publikowanie komentarza, ponieważ inni odpowiedzieli już lepiej niż mogłem poniżej :)
davr

5
Ale o to chodzi w tym, żeby nie być wbudowanym, musisz to zrobić sam. Rzeczywistość od wysokiej do niskiej: wbudowana, biblioteka, domowe. Sam używam C ++, a na pewno nie nienawidzę, ponieważ jest to najlepszy język na świecie. Ale dynamiczne zarządzanie pamięcią jest uciążliwe.
QBziZ

4
@Davr - nie nienawidzę C ++ ani nawet nie próbuję argumentować, że C ++ potrzebuje śmieciarza. Pytam, bo wiem, że Bjarne Stroustrup powiedział, że BĘDZIE dodane, i byłam ciekawa, jakie były powody jego niewdrożenia.
Jason Baker

1
W tym artykule Boehm Collector for C i C ++ autorstwa Dr. Dobbs opisuje otwarty moduł śmieciowy, który może być używany zarówno z C, jak i C ++. Omówiono niektóre problemy, które pojawiają się podczas używania śmieciarza z destruktorami C ++, a także C Standard Library.
Richard Chambers

1
@rogerdpack: Ale do tej pory nie jest tak przydatny (patrz moja odpowiedź ...), więc jest mało prawdopodobne, że implementacje zainwestują w posiadanie takiego.
einpoklum

Odpowiedzi:


160

Można było dodać niejawne wyrzucanie elementów bezużytecznych, ale to po prostu nie spowodowało cięcia. Prawdopodobnie z powodu nie tylko komplikacji związanych z wdrożeniem, ale także z powodu niemożności szybkiego osiągnięcia ogólnego konsensusu.

Cytat samego Bjarne Stroustrup:

Miałem nadzieję, że moduł wyrzucania elementów bezużytecznych, który można opcjonalnie włączyć, będzie częścią C ++ 0x, ale było wystarczająco dużo problemów technicznych, które muszę poradzić sobie ze szczegółową specyfikacją tego, jak taki kolektor integruje się z resztą języka , jeśli podano. Podobnie jak w przypadku zasadniczo wszystkich funkcji C ++ 0x, istnieje eksperymentalna implementacja.

Dobra dyskusja na ten temat tutaj .

Przegląd ogólny:

C ++ jest bardzo wydajny i pozwala robić prawie wszystko. Z tego powodu nie wypycha automatycznie wielu rzeczy, które mogą mieć wpływ na wydajność. Odśmiecanie można łatwo wdrożyć za pomocą inteligentnych wskaźników (obiektów, które zawijają wskaźniki z liczbą referencji, które automatycznie usuwają się, gdy liczba referencji osiągnie 0).

C ++ został zbudowany z myślą o konkurentach, którzy nie mieli funkcji usuwania śmieci. Wydajność była głównym problemem, przed którym C ++ musiał odpierać krytykę w porównaniu do C i innych.

Istnieją 2 rodzaje śmiecia ...

Jawne wyrzucanie elementów bezużytecznych:

C ++ 0x będzie mieć wyrzucanie elementów bezużytecznych za pomocą wskaźników utworzonych za pomocą shared_ptr

Jeśli chcesz, możesz go użyć, jeśli nie chcesz, nie musisz go używać.

Możesz teraz użyć boost: shared_ptr, jeśli nie chcesz czekać na C ++ 0x.

Ukryte usuwanie śmieci:

Nie ma jednak przezroczystego wyrzucania elementów bezużytecznych. Będzie to jednak punkt skupienia dla przyszłych specyfikacji C ++.

Dlaczego Tr1 nie ma niejawnego wyrzucania elementów bezużytecznych?

Jest wiele rzeczy, które powinna mieć tr1 z C ++ 0x, Bjarne Stroustrup w poprzednich wywiadach stwierdził, że tr1 nie ma tyle, ile by chciał.


71
Chciałbym stać wróg czy C ++ zmuszony zbieranie śmieci na mnie! Dlaczego ludzie nie mogą korzystać smart_ptr's? Jak zrobiłbyś rozwidlanie niskiego poziomu w stylu uniksowym, z przeszkodą na śmieci? Może to mieć wpływ na inne rzeczy, takie jak wątki. Python ma globalną blokadę interpretera, głównie z powodu wyrzucania elementów bezużytecznych (patrz Cython). Trzymaj go z dala od C / C ++, dzięki.
unixman83,

26
@ unixman83: Głównym problemem związanym z liczeniem odwołań do śmieci (tj. std::shared_ptr) są cykliczne odwołania, które powodują wyciek pamięci. Dlatego musisz ostrożnie używać, std::weak_ptraby przerywać cykle, co jest bałagan. Styl znakowania i wobulacji GC nie ma tego problemu. Nie ma nieodłącznej niezgodności między wątkami / rozwidlaniem a odśmiecaniem. Zarówno Java, jak i C # mają wielowątkowość zapobiegawczą o wysokiej wydajności oraz moduł wyrzucający elementy bezużyteczne. Istnieją problemy związane z aplikacjami działającymi w czasie rzeczywistym i urządzeniem do wyrzucania elementów bezużytecznych, ponieważ większość urządzeń do usuwania elementów wyrzucających śmieci musi zatrzymać cały świat.
Andrew Tomazos,

9
„Głównym problemem związanym z liczeniem odwołań do śmieci (tj. std::shared_ptr) Są cykliczne odwołania” i okropne działanie, które jest ironiczne, ponieważ lepsza wydajność jest zwykle uzasadnieniem użycia C ++ ... flyingfrogblog.blogspot.co.uk/2011/01/…
Jon Harrop,

11
„Jak zrobiłbyś rozwiązywanie niskiego poziomu w stylu uniksowym”. W ten sam sposób robią to GC'd, takie jak OCaml, od około 20 lat lub dłużej.
Jon Harrop,

9
„Python ma globalną blokadę interpretera głównie z powodu wyrzucania elementów bezużytecznych”. Argument Strawmana. Zarówno Java, jak i .NET mają GC, ale nie mają globalnych blokad.
Jon Harrop,

149

Aby dodać do debaty tutaj.

Znane są problemy z odśmiecaniem pamięci, a ich zrozumienie pomaga zrozumieć, dlaczego nie ma go w C ++.

1. Wydajność?

Pierwszy zarzut dotyczy często wydajności, ale większość ludzi nie zdaje sobie sprawy z tego, o czym mówi. Jak ilustrujeMartin Beckett ten problem, może nie występować sama w sobie wydajność, ale przewidywalność wydajności.

Obecnie istnieją 2 rodziny GC, które są szeroko wdrażane:

  • Rodzaj Mark-And-Sweep
  • Rodzaj liczenia referencji

The Mark And SweepJest szybszy (mniejszy wpływ na ogólną wydajność), ale cierpi z powodu „zamrozić świat” syndromu: czyli gdy kopnięcia w GC, wszystko inne jest zatrzymany, aż GC dokonał jego oczyszczanie. Jeśli chcesz zbudować serwer, który odpowie w ciągu kilku milisekund ... niektóre transakcje nie spełnią Twoich oczekiwań :)

Problem Reference Countingjest inny: zliczanie referencji zwiększa obciążenie, szczególnie w środowiskach wielowątkowych, ponieważ musisz mieć liczbę atomową. Ponadto istnieje problem cykli odniesienia, dlatego potrzebny jest sprytny algorytm do wykrywania tych cykli i ich eliminowania (zazwyczaj wdrażany również przez „zamrożenie świata”, choć rzadziej). Ogólnie rzecz biorąc, na dzień dzisiejszy ten rodzaj (chociaż zwykle bardziej responsywny lub raczej zamrażający rzadziej) jest wolniejszy niż Mark And Sweep.

Widziałem artykuł realizatorów Eiffla, którzy próbowali wdrożyć Reference CountingGarbage Collectora, który miałby podobną globalną wydajność Mark And Sweepbez aspektu „Freeze The World”. Wymagało to osobnego wątku dla GC (typowe). Algorytm był nieco przerażający (na końcu), ale dokument dobrze się spisał, wprowadzając koncepcje pojedynczo i pokazując ewolucję algorytmu od wersji „prostej” do pełnej. Polecam lekturę, gdybym tylko mógł odłożyć ręce na plik PDF ...

2. Pozyskiwanie zasobów to inicjalizacja (RAII)

Jest to powszechny idiom C++polegający na tym, że otaczasz własność zasobów w obiekcie, aby zapewnić ich prawidłowe zwolnienie. Jest używany głównie do pamięci, ponieważ nie mamy wyrzucania elementów bezużytecznych, ale jest także użyteczny w wielu innych sytuacjach:

  • zamki (wielowątkowe, uchwyt pliku, ...)
  • połączenia (z bazą danych, innym serwerem, ...)

Chodzi o to, aby właściwie kontrolować żywotność obiektu:

  • powinien być żywy tak długo, jak go potrzebujesz
  • należy go zabić, kiedy skończysz

Problem GC polega na tym, że jeśli pomaga on w tym pierwszym i ostatecznie gwarantuje, że później ... to „ostateczne” może nie być wystarczające. Jeśli zwolnisz blokadę, naprawdę chcesz, aby została zwolniona teraz, aby nie blokowała żadnych dalszych połączeń!

Języki z GC mają dwa sposoby obejścia:

  • nie używaj GC, gdy alokacja stosu jest wystarczająca: zwykle dotyczy problemów z wydajnością, ale w naszym przypadku naprawdę pomaga, ponieważ zakres określa czas życia
  • usingkonstruuj ... ale jest jawny (słaby) RAII w C ++ RAII jest niejawny, więc użytkownik NIE MOŻE nieświadomie popełnić błędu (pomijając usingsłowo kluczowe)

3. Inteligentne wskaźniki

Inteligentne wskaźniki często pojawiają się jako srebrna kula do obsługi pamięci C++. Często słyszałem: w końcu nie potrzebujemy GC, ponieważ mamy inteligentne wskaźniki.

Nie można się bardziej mylić.

Inteligentne wskaźniki pomagają: auto_ptri unique_ptrwykorzystują koncepcje RAII, naprawdę bardzo przydatne. Są tak proste, że można je łatwo napisać samodzielnie.

Jednak gdy trzeba współdzielić własność, staje się to trudniejsze: możesz współdzielić wiele wątków i istnieje kilka subtelnych problemów z obsługą liczenia. Dlatego w naturalny sposób dąży się do shared_ptr.

To świetnie, po to w końcu Boost, ale to nie jest srebrna kula. W rzeczywistości głównym problemem shared_ptrjest to, że emuluje on GC zaimplementowany przez, Reference Countingale musisz samodzielnie wdrożyć wykrywanie cyklu ... Urg

Oczywiście jest coś takiego weak_ptr, ale niestety już widziałem przecieki pamięci pomimo użycia z shared_ptrpowodu tych cykli ... a kiedy jesteś w środowisku wielowątkowym, niezwykle trudno go wykryć!

4. Jakie jest rozwiązanie?

Nie ma srebrnej kuli, ale jak zawsze jest to wykonalne. W przypadku braku GC należy jasno określić własność:

  • wolę mieć jednego właściciela w danym momencie, jeśli to możliwe
  • jeśli nie, upewnij się, że twój diagram klas nie ma żadnego cyklu dotyczącego własności i przerwij je subtelnym zastosowaniem weak_ptr

Tak więc byłoby wspaniale mieć GC ... jednak nie jest to trywialny problem. A tymczasem musimy tylko podwinąć rękawy.


2
Chciałbym zaakceptować dwie odpowiedzi! To jest po prostu świetne. Jedną rzeczą, na którą należy zwrócić uwagę, jeśli chodzi o wydajność, GC działający w osobnym wątku jest w rzeczywistości dość powszechny (jest używany w Javie i .Net). To prawda, że ​​może nie być to dopuszczalne w systemach wbudowanych.
Jason Baker,

14
Tylko dwa typy? Co powiesz na kopiowanie kolektorów? Kolektory pokoleniowe? Różne współbieżne kolektory (w tym twarda bieżnia Bakera w czasie rzeczywistym)? Różne hybrydowe kolektory? Człowieku, sama ignorancja w branży w tej dziedzinie czasami mnie zaskakuje.
PO PROSTU MOJA poprawna OPINIA

12
Czy powiedziałem, że były tylko 2 typy? Powiedziałem, że 2 były szeroko rozmieszczone. O ile wiem Python, Java i C # wszystkie używają teraz algorytmów Mark i Sweep (Java miała algorytm zliczania referencji). Mówiąc dokładniej, wydaje mi się, że C # używa Generational GC do mniejszych cykli, Mark And Sweep do większych cykli i Copying do walki z fragmentacją pamięci; choć argumentowałbym, że sercem algorytmu jest Mark And Sweep. Czy znasz jakiś główny język, który korzysta z innej technologii? Zawsze chętnie się uczę.
Matthieu M.

3
Właśnie wymieniłeś jeden główny język, który używał trzech.
PO PROSTU MOJA poprawna OPINIA

3
Główną różnicą jest to, że Generacyjne i Przyrostowe GC nie muszą zatrzymywać świata do działania, i możesz sprawić, aby działały na systemach jednowątkowych bez nadmiernego obciążenia, od czasu do czasu wykonując iteracje przejścia drzewa podczas uzyskiwania dostępu do wskaźników GC (czynnik można określić na podstawie liczby nowych węzłów wraz z podstawową prognozą potrzeby gromadzenia). Możesz posunąć GC jeszcze dalej, dołączając dane o tym, gdzie w kodzie doszło do utworzenia / modyfikacji węzła, co może pozwolić ci poprawić swoje przewidywania, i otrzymasz za nim analizę ucieczki.
Keldon Alleyne,

56

Jakiego typu? czy należy go zoptymalizować pod kątem wbudowanych sterowników pralek, telefonów komórkowych, stacji roboczych lub superkomputerów?
Czy priorytetem powinno być reagowanie na gui lub ładowanie serwera?
czy powinien zużywać dużo pamięci, czy dużo procesora?

C / c ++ jest używany w zbyt wielu różnych okolicznościach. Podejrzewam, że dla większości użytkowników wystarczy coś w rodzaju inteligentnych wskaźników doładowania

Edycja - automatyczne śmieciarki nie są tak bardzo problemem wydajności (zawsze możesz kupić więcej serwerów), to kwestia przewidywalnej wydajności.
Nie wiedzieć, kiedy GC ma się rozpocząć, to jak zatrudnienie narkoleptycznego pilota linii lotniczych, w większości przypadków są świetne - ale kiedy naprawdę potrzebujesz czasu reakcji!


6
Zdecydowanie rozumiem twój punkt widzenia, ale czuję się zmuszony zapytać: czy Java nie jest używana w tak wielu aplikacjach?
Jason Baker

35
Nie. Java nie nadaje się do aplikacji o wysokiej wydajności z tego prostego powodu, że nie ma gwarancji wydajności w takim samym stopniu jak C ++. Więc znajdziesz go w telefonie komórkowym, ale nie znajdziesz go w przełączniku komórki lub superkomputerze.
Zathrus,

11
Zawsze możesz kupić więcej serwerów, ale nie zawsze możesz kupić więcej CPU dla telefonu komórkowego już w kieszeni klienta!
Crashworks,

8
Java znacznie poprawiła wydajność procesora. Naprawdę trudnym problemem jest użycie pamięci, Java jest z natury mniej wydajna pod względem pamięci niż C ++. A ta nieefektywność wynika z faktu, że są one zbierane śmieci. Odśmiecanie nie może być zarówno szybkie, jak i efektywne pod względem pamięci, co staje się oczywiste, jeśli spojrzy się na szybkość działania algorytmów GC.
Nate CK,

2
@Zathrus java może wygrać z przepustowością b / c jit optymalizującego, ale nie z opóźnieniem (boo w czasie rzeczywistym), a na pewno nie z pamięci.
gtrak

34

Jednym z głównych powodów, dla których C ++ nie ma wbudowanej funkcji czyszczenia pamięci, jest to, że bardzo trudno jest sprawić, by funkcja czyszczenia pamięci dobrze działała z destruktorami. O ile mi wiadomo, nikt tak naprawdę nie wie, jak to rozwiązać całkowicie. Istnieje wiele problemów do rozwiązania:

  • deterministyczne czasy życia obiektów (daje to zliczanie referencji, ale GC tego nie robi. Chociaż może to nie być aż tak duże).
  • co się stanie, jeśli niszczyciel wyrzuci podczas zbierania śmieci? Większość języków ignoruje ten wyjątek, ponieważ tak naprawdę nie ma bloku blokującego, który mógłby go przetransportować, ale prawdopodobnie nie jest to akceptowalne rozwiązanie dla C ++.
  • Jak włączyć / wyłączyć? Oczywiście prawdopodobnie byłaby to decyzja o czasie kompilacji, ale kod napisany dla GC vs kod napisany dla NOT GC będzie bardzo różny i prawdopodobnie niekompatybilny. Jak to pogodzisz?

To tylko niektóre z napotkanych problemów.


17
GC i destruktory to rozwiązany problem przez miły boczny krok od Bjarne. Niszczyciele nie działają podczas GC, ponieważ nie o to chodzi w GC. GC w C ++ istnieje po to, aby stworzyć pojęcie nieskończonej pamięci , a nie nieskończonych innych zasobów.
MSalters

2
Jeśli destruktory nie działają, to całkowicie zmienia semantykę języka. Wydaje mi się, że przynajmniej potrzebujesz nowego słowa kluczowego „gcnew” lub czegoś takiego, abyś wyraźnie zezwolił na GC'ed dla tego obiektu (i dlatego nie powinieneś używać go do zawijania zasobów poza pamięcią).
Greg Rogers

7
To fałszywy argument. Ponieważ C ++ ma jawne zarządzanie pamięcią, musisz dowiedzieć się, kiedy każdy obiekt musi zostać uwolniony. Z GC nie ma gorzej; problem sprowadza się raczej do ustalenia, kiedy pewne obiekty są uwalniane, a mianowicie tych, które wymagają specjalnego rozważenia po usunięciu. Doświadczenie w programowaniu w Javie i C # ujawnia, że ​​zdecydowana większość obiektów nie wymaga żadnych specjalnych rozważań i może być bezpiecznie pozostawiona GC. Jak się okazuje, jedną z głównych funkcji destruktorów w C ++ jest uwalnianie obiektów potomnych, które GC obsługuje dla ciebie automatycznie.
Nate CK

2
@ NateC-K: Jedną rzeczą, która została ulepszona w GC w porównaniu z nie-GC (być może największą rzeczą), jest zdolność solidnego systemu GC do zagwarantowania, że ​​każde odniesienie będzie nadal wskazywało ten sam obiekt, dopóki odniesienie istnieje. Wywołanie Disposeobiektu może sprawić, że stanie się ono niestabilne, ale odniesienia, które wskazywały na obiekt, kiedy był żywy, będą to robić po jego śmierci. Natomiast w systemach innych niż GC obiekty mogą być usuwane, dopóki istnieją odniesienia, i rzadko istnieje jakakolwiek granica spustoszenia, która może zostać zniesiona, jeśli jedno z tych odniesień zostanie wykorzystane.
supercat

22

Chociaż jest to stare pytanie, wciąż nie widzę problemu, z którym nikt by się nie zajął: wyrzucanie elementów bezużytecznych jest prawie niemożliwe.

W szczególności standard C ++ dość ostrożnie określa język pod kątem zachowania obserwowalnego z zewnątrz, a nie sposobu, w jaki implementacja osiąga to zachowanie. W przypadku zbierania śmieci, jednak nie ma praktycznie żadnego zewnętrznie obserwowalne zachowanie.

Ogólna idea zbierania śmieci jest, że powinien wykonać odpowiednią próbę zapewnienia, że alokacja pamięci uda. Niestety, w zasadzie niemożliwe jest zagwarantowanie, że jakakolwiek alokacja pamięci zakończy się powodzeniem, nawet jeśli masz działający moduł czyszczenia pamięci. Jest to do pewnego stopnia prawdziwe w każdym przypadku, ale szczególnie w przypadku C ++, ponieważ (prawdopodobnie) nie jest możliwe użycie kolektora kopiującego (lub czegoś podobnego), który porusza obiekty w pamięci podczas cyklu zbierania.

Jeśli nie możesz przenosić obiektów, nie możesz utworzyć pojedynczej, ciągłej przestrzeni pamięci, z której będziesz dokonywać alokacji - a to oznacza, że ​​twoja sterta (lub darmowy magazyn lub jakkolwiek chcesz to nazwać) może i prawdopodobnie będzie z czasem ulegają fragmentacji. To z kolei może uniemożliwić powodzenie alokacji, nawet jeśli jest wolna pamięć niż żądana ilość.

Chociaż może być możliwe uzyskanie pewnej gwarancji, która mówi (w istocie), że jeśli powtórzysz dokładnie ten sam wzorzec alokacji, i to się powiedzie za pierwszym razem, będzie nadal odnosić sukcesy w kolejnych iteracjach, pod warunkiem, że przydzielona pamięć stał się niedostępny między iteracjami. To taka słaba gwarancja, że ​​jest zasadniczo bezużyteczna, ale nie widzę żadnej uzasadnionej nadziei na jej wzmocnienie.

Mimo to jest silniejszy niż to, co zostało zaproponowane dla C ++. Poprzednia propozycja [ostrzeżenie: PDF] (który dostał spadł) nie gwarantuje nic. Na 28 stronach propozycji to, co przeszkadzało ci w obserwowaniu z zewnątrz, to pojedyncza (nienormatywna) notatka:

[Uwaga: w przypadku śmieciowych programów wysokiej jakości hostowana implementacja powinna próbować zmaksymalizować ilość odzyskanej pamięci nieosiągalnej. —Wskazówka]

Przynajmniej dla mnie rodzi to poważne pytanie dotyczące zwrotu z inwestycji. Zniszczymy istniejący kod (nikt nie jest pewien, ile dokładnie, ale na pewno całkiem sporo), nałożymy nowe wymagania na implementacje i nowe ograniczenia w kodzie, a co w zamian otrzymamy, może wcale nie jest niczym?

Nawet w najlepszym wypadku otrzymujemy programy, które w oparciu o testy z Javą prawdopodobnie będą potrzebowały około sześć razy więcej pamięci do działania z tą samą prędkością, co teraz. Co gorsza, odśmiecanie było częścią Javy od samego początku - C ++ nakłada wystarczająco więcej ograniczeń na moduł odśmiecający, że prawie na pewno będzie mieć jeszcze gorsze stosunek kosztów do korzyści (nawet jeśli wykroczymy poza to, co gwarantuje propozycja i zakładamy, że będzie niektóre korzyści).

Podsumuję sytuację matematycznie: to złożona sytuacja. Jak każdy matematyk wie, liczba złożona składa się z dwóch części: rzeczywistej i wyobrażonej. Wydaje mi się, że mamy tutaj rzeczywiste koszty, ale korzyści (przynajmniej w większości) wymyślone.


Sądzę, że nawet jeśli ktoś określi, że do poprawnego działania wszystkie obiekty muszą zostać usunięte, a tylko obiekty, które zostały usunięte, kwalifikowałyby się do zbierania, obsługa kompilatora do wyrzucania elementów bezużytecznych śledzenia referencji może być nadal przydatna, ponieważ taki język mógłby zapewnić użycie usuniętego wskaźnika (odniesienia) z pewnością zatrzyma, a nie spowoduje niezdefiniowane zachowanie.
supercat

2
Nawet w Javie GC nie jest tak naprawdę wyspecyfikowane, by robić cokolwiek przydatnego AFAIK. Może freecię wezwać (gdzie mam na myśli freejęzyk analogiczny do języka C). Ale Java nigdy nie gwarantuje, że zadzwoni do finalizatorów lub czegoś podobnego. W rzeczywistości C ++ robi znacznie więcej niż Java, aby ominąć zapisywanie bazy danych zatwierdzania, opróżnianie uchwytów plików i tak dalej. Java twierdzi, że ma „GC”, ale programiści Java muszą skrupulatnie dzwonić close()cały czas i muszą być bardzo świadomi zarządzania zasobami, uważając, aby nie zadzwonić close()zbyt wcześnie lub za późno. C ++ nas od tego uwalnia. ... (ciąg dalszy)
Aaron McDaid

2
.. mój komentarz przed chwilą nie ma na celu krytykowania Javy. Po prostu obserwuję, że termin „wywóz śmieci” jest bardzo dziwnym terminem - oznacza o wiele mniej, niż ludzie myślą, i dlatego trudno jest dyskutować bez jasnego znaczenia.
Aaron McDaid

@AaronMcDaid To prawda, że ​​GC wcale nie pomaga w przypadku zasobów innych niż pamięć. Na szczęście takie zasoby przydzielane są bardzo rzadko w porównaniu do pamięci. Co więcej, ponad 90% z nich można uwolnić w metodzie, która je przydzieliła, więc try (Whatever w=...) {...}rozwiązuje to (a otrzymasz ostrzeżenie, gdy zapomnisz). Pozostałe są również problematyczne z RAII. Nazywanie close()„cały czas” oznacza może raz na dziesiątki tysięcy linii, więc nie jest tak źle, podczas gdy pamięć jest przydzielana prawie na każdą linię Java.
maaartinus

15

Jeśli chcesz automatycznego wyrzucania elementów bezużytecznych, istnieją dobre komercyjne i publiczne obiekty do zbierania elementów bezużytecznych dla C ++. W aplikacjach, w których odpowiednie jest czyszczenie pamięci, C ++ jest doskonałym językiem usuwania śmieci, którego wydajność jest porównywalna z innymi językami usuwania śmieci. Zobacz C ++ Programming Language (wydanie 4), aby zapoznać się z dyskusją na temat automatycznego usuwania śmieci w C ++. Zobacz także Hans-J. Witryna Boehm do zbierania pamięci w języku C i C ++ ( archiwum ).

Ponadto C ++ obsługuje techniki programowania, które pozwalają na bezpieczne zarządzanie pamięcią i niejawne bez modułu wyrzucania elementów bezużytecznych . Uważam, że wyrzucanie elementów bezużytecznych jest ostatnim wyborem i niedoskonałym sposobem zarządzania zasobami. Nie oznacza to, że nigdy nie jest użyteczne, po prostu lepsze podejście w wielu sytuacjach.

Źródło: http://www.stroustrup.com/bs_faq.html#garbage-collection

Jeśli chodzi o to, dlaczego nie ma go wbudowane, jeśli dobrze pamiętam, zostało wynalezione zanim GC było rzeczą i nie sądzę, że język mógł mieć GC z kilku powodów (IE kompatybilny wstecz z C)

Mam nadzieję że to pomoże.


„z wydajnością, która wypada korzystnie w porównaniu z innymi językami zbierania śmieci”. Cytat?
Jon Harrop,

1
Mój link został uszkodzony. Napisałem tę odpowiedź 5 lat temu.
Rayne

1
Ok, liczyłem na pewną niezależną weryfikację tych twierdzeń, tj. Nie przez Stroustrup czy Boehm. :-)
Jon Harrop,

12

Stroustrup skomentował to na konferencji Going Native 2013.

Wystarczy przejść do około 25m50 w tym filmie . (Właściwie polecam obejrzenie całego filmu, ale przechodzę do rzeczy związanych z odśmiecaniem).

Gdy masz naprawdę świetny język, który ułatwia (i jest bezpieczny, przewidywalny, łatwy do odczytania i łatwy do nauczenia) zajmowanie się przedmiotami i wartościami w bezpośredni sposób, unikając (jawnego) użycia sterty, to nawet nie chcesz śmieci.

W nowoczesnym C ++ oraz w C ++ 11 odśmiecanie nie jest już pożądane, z wyjątkiem ograniczonych okoliczności. W rzeczywistości, nawet jeśli dobry śmieciarz jest wbudowany w jeden z głównych kompilatorów C ++, myślę, że nie będzie on używany zbyt często. Będzie to łatwiejsze , nie ciężej, aby uniknąć GC.

Pokazuje ten przykład:

void f(int n, int x) {
    Gadget *p = new Gadget{n};
    if(x<100) throw SomeException{};
    if(x<200) return;
    delete p;
}

Jest to niebezpieczne w C ++. Ale w Javie jest to również niebezpieczne! W C ++, jeśli funkcja zwraca wcześniej, deletenigdy nie zostanie wywołana. Ale jeśli masz pełne wyrzucanie elementów bezużytecznych, na przykład w Javie, otrzymujesz jedynie sugestię, że obiekt zostanie zniszczony „w pewnym momencie w przyszłości” ( aktualizacja: jest nawet gorzej niż to. Java robi nieobiecaj, że zadzwonisz do finalizatora kiedykolwiek - może nigdy nie zostanie wywołany). Nie jest to wystarczające, jeśli gadżet posiada otwarty uchwyt pliku, połączenie z bazą danych lub dane, które zostały później buforowane do zapisu w bazie danych. Chcemy, aby gadżet został zniszczony, gdy tylko się skończy, aby jak najszybciej uwolnić te zasoby. Nie chcesz, aby Twój serwer bazy danych borykał się z tysiącami połączeń z bazą danych, które nie są już potrzebne - nie wie, że twój program jest gotowy do pracy.

Więc jakie jest rozwiązanie? Istnieje kilka podejść. Oczywistym podejściem, które zastosujesz w zdecydowanej większości swoich obiektów, jest:

void f(int n, int x) {
    Gadget p = {n};  // Just leave it on the stack (where it belongs!)
    if(x<100) throw SomeException{};
    if(x<200) return;
}

Pisanie zajmuje mniej znaków. Nie przeszkadza new. Nie wymaga Gadgetdwukrotnego pisania . Obiekt jest niszczony na końcu funkcji. Jeśli tego właśnie chcesz, jest to bardzo intuicyjne. Gadgetzachowują się tak samo jak intlub double. Przewidywalny, łatwy do odczytania, łatwy do nauczenia. Wszystko jest „wartością”. Czasami duża wartość, ale wartości są łatwiejsze do nauczenia, ponieważ nie masz tej „akcji na odległość”, którą otrzymujesz ze wskaźnikami (lub referencjami).

Większość tworzonych obiektów jest wykorzystywana tylko w funkcji, która je utworzyła, i być może przekazywana jako dane wejściowe do funkcji potomnych. Programiści nie powinni myśleć o „zarządzaniu pamięcią” podczas zwracania obiektów lub w inny sposób udostępniania obiektów w szeroko oddzielnych częściach oprogramowania.

Ważny jest zakres i czas życia. W większości przypadków łatwiej jest, jeśli czas życia jest taki sam jak zakres. Łatwiej to zrozumieć i łatwiej uczyć. Jeśli chcesz mieć inny czas życia, powinno być oczywiste, że czytasz kod, który robisz, shared_ptrna przykład za pomocą. (Lub zwracając (duże) obiekty według wartości, wykorzystując semantykę move lub unique_ptr.

Może to wydawać się problemem wydajności. Co jeśli chcę zwrócić gadżet foo()? Semantyka ruchów w C ++ 11 ułatwia zwracanie dużych obiektów. Po prostu napisz, Gadget foo() { ... }a będzie działać i działać szybko. Nie musisz się z &&sobą bawić, po prostu zwracaj wartości według wartości, a język często będzie w stanie dokonać niezbędnych optymalizacji. (Nawet przed C ++ 03 kompilatory wykonały wyjątkowo dobrą robotę, unikając niepotrzebnego kopiowania.)

Jak Stroustrup powiedział w innym miejscu filmu (parafrazując): „Tylko informatyk nalegałby na skopiowanie obiektu, a następnie zniszczenie oryginału. (Śmiech publiczności). Dlaczego nie przenieść obiektu bezpośrednio w nowe miejsce? To właśnie ludzie (nie informatycy) oczekują ”.

Gdy możesz zagwarantować, że potrzebna jest tylko jedna kopia obiektu, o wiele łatwiej jest zrozumieć jego żywotność. Możesz wybrać, jakie zasady na całe życie chcesz, a usuwanie śmieci jest dostępne, jeśli chcesz. Ale kiedy zrozumiesz zalety innych podejść, zauważysz, że wyrzucanie elementów bezużytecznych znajduje się na dole listy preferencji.

Jeśli to nie zadziała, można użyć unique_ptr, lub w przypadku jego braku, shared_ptr. Dobrze napisany C ++ 11 jest krótszy, łatwiejszy do odczytania i łatwiejszy do nauczenia niż wiele innych języków, jeśli chodzi o zarządzanie pamięcią.


1
GC należy używać wyłącznie do obiektów, które nie nabywają zasobów (tj. Proszą inne podmioty o robienie rzeczy w ich imieniu „do odwołania”). Jeśli Gadgetnie prosi o nic innego w jego imieniu, oryginalny kod byłby całkowicie bezpieczny w Javie, gdyby deleteusunięto bezsensowną instrukcję (do Java) .
supercat

@ Supercat, obiekty z nudnymi niszczycielami są interesujące. (Nie zdefiniowałem „nudnego”, ale w zasadzie niszczycieli, których nigdy nie trzeba wywoływać, z wyjątkiem uwolnienia pamięci). Może być możliwe, że pojedynczy kompilator traktuje shared_ptr<T>specjalnie, gdy Tjest „nudny”. Może zdecydować się nie zarządzać licznikiem referencji dla tego typu i zamiast tego użyć GC. Pozwoliłoby to na użycie GC bez powiadomienia dewelopera. Odpowiedni shared_ptrmoże być po prostu postrzegany jako wskaźnik GC T. Ale są w tym ograniczenia i spowolniłoby to wiele programów.
Aaron McDaid,

Dobry system typów powinien mieć różne typy dla obiektów sterowanych GC i RAII, ponieważ niektóre wzorce użytkowania działają bardzo dobrze z jednym, a bardzo słabo z drugim. W .NET lub Java, oświadczenie string1=string2;będzie wykonać bardzo szybko, niezależnie od długości łańcucha (to dosłownie nic więcej niż obciążenia rejestru i zarejestrować sklep) i nie wymaga żadnej blokady, aby zapewnić, że jeśli powyższe stwierdzenie jest wykonywany podczas string2Is podczas zapisywania string1będzie zawierał starą lub nową wartość, bez niezdefiniowanego zachowania).
supercat

W C ++ przypisanie a shared_ptr<String>wymaga dużej synchronizacji za kulisami, a przypisanie a Stringmoże zachowywać się dziwnie, jeśli zmienna jest odczytywana i zapisywana jednocześnie. Przypadki, w których ktoś chciałby pisać i czytać Stringjednocześnie, nie są strasznie częste, ale mogą powstać, jeśli np. Jakiś kod chce udostępnić bieżące raporty o stanie innym wątkom. W .NET i Javie takie rzeczy po prostu „działają”.
supercat

1
@curiousguy nic się nie zmieniło, chyba że podejmiesz odpowiednie środki ostrożności, Java nadal pozwala na wywołanie finalizatora, gdy tylko konstruktor zakończy działanie. Oto przykład z prawdziwego życia: „ finalize () wywołał obiekty o wysokim zasięgu, które można osiągnąć w Javie 8 ”. Wniosek jest taki, że nigdy nie należy korzystać z tej funkcji, że prawie wszyscy zgadzają się na historyczny błąd językowy projektu. Postępując zgodnie z tą radą, język stanowi determinizm, który kochamy.
Holger,

11

Ponieważ nowoczesny C ++ nie wymaga wyrzucania elementów bezużytecznych.

Odpowiedź Bjarne Stroustrup w tej sprawie brzmi :

Nie lubię śmieci. Nie lubię śmiecić. Moim ideałem jest wyeliminowanie potrzeby wyrzucania elementów bezużytecznych. To jest teraz możliwe.


Sytuacja w przypadku kodu napisanego w tych dniach (C ++ 17 i zgodnie z oficjalnymi podstawowymi wytycznymi ) jest następująca:

  • Większość kodu związanego z własnością pamięci znajduje się w bibliotekach (szczególnie tych udostępniających kontenery).
  • Większość wykorzystania kodu obejmującego własność pamięci jest zgodna ze wzorcem RAII , więc alokacja jest dokonywana przy budowie i dezalokacji przy zniszczeniu, co ma miejsce przy wychodzeniu z zakresu, w którym coś zostało przydzielone.
  • Nie przydzielasz bezpośrednio ani nie zwalniasz bezpośrednio pamięci .
  • Surowe wskaźniki nie posiadają pamięci (jeśli postępujesz zgodnie z wytycznymi), więc nie możesz przeciekać, przekazując je.
  • Jeśli zastanawiasz się, w jaki sposób przekażesz początkowe adresy sekwencji wartości w pamięci - zrobisz to z rozpiętością ; nie jest potrzebny surowy wskaźnik.
  • Jeśli naprawdę potrzebujesz własnego „wskaźnika”, korzystasz ze standardowych wskaźników inteligentnych biblioteki C ++ - nie mogą one przeciekać i są całkiem wydajne (chociaż ABI może temu przeszkodzić ). Alternatywnie możesz przekazać własność ponad granicami zakresu za pomocą „wskaźników właściciela” . Są to rzadkie przypadki i muszą być użyte jawnie; ale przyjęte - pozwalają na ładną kontrolę statyczną pod kątem wycieków.

„Och tak? Ale co z…

... jeśli po prostu napiszę kod w taki sposób, w jaki pisaliśmy C ++ w dawnych czasach? "

Rzeczywiście, to mógłby po prostu zignorować wszystkie zalecenia i napisać nieszczelny kod aplikacji - i będzie to skompilować i uruchomić (i nieszczelności), tak samo jak zawsze.

Ale nie jest to sytuacja „po prostu nie rób tego”, w której oczekuje się, że programista będzie cnotliwy i wykaże wiele samokontroli; po prostu pisanie niezgodnego kodu nie jest prostsze, pisanie nie jest szybsze, ani nie jest bardziej wydajne. Stopniowo będzie też trudniej pisać, ponieważ napotkasz rosnące „niedopasowanie impedancji” w zależności od tego, co zapewnia i czego oczekuje zgodny kod.

... jeśli ja reintrepret_cast? A może skomplikowana arytmetyka wskaźników? Lub inne takie hacki? ”

Rzeczywiście, jeśli zdecydujesz się na to, możesz napisać kod, który psuje rzeczy, mimo że bawisz się zgodnie z wytycznymi. Ale:

  1. Robiłbyś to rzadko (pod względem miejsc w kodzie, niekoniecznie pod względem ułamka czasu wykonania)
  2. Zrobiłbyś to tylko celowo, a nie przypadkowo.
  3. Takie postępowanie wyróżnia się w bazie kodu zgodnej z wytycznymi.
  4. Jest to rodzaj kodu, w którym i tak pominiesz GC w innym języku.

... rozwój biblioteki? ”

Jeśli jesteś programistą biblioteki C ++, piszesz niebezpieczny kod zawierający surowe wskaźniki, a ty musisz kodować ostrożnie i odpowiedzialnie - ale są to samodzielne fragmenty kodu napisane przez ekspertów (i, co ważniejsze, recenzowane przez ekspertów).


Tak więc, tak jak powiedział Bjarne: Generalnie nie ma motywacji do zbierania śmieci, ponieważ wszyscy starajcie się nie produkować śmieci. GC staje się problemem w C ++.

Nie oznacza to, że GC nie jest interesującym problemem dla niektórych konkretnych aplikacji, gdy chcesz zastosować niestandardowe strategie alokacji i alokacji. Dla tych, którzy chcieliby mieć niestandardowy przydział i przydział, a nie GC na poziomie języka.


Cóż, potrzebuje (potrzebuje GC), jeśli szlifujesz łańcuchy. Wyobraź sobie, że masz duże tablice łańcuchów (pomyśl setki megabajtów), które budujesz fragmentarycznie, a następnie przetwarzasz i przebudowujesz na różne długości, usuwając nieużywane, łącząc inne itp. I wiem, ponieważ musiałem przejść na języki wysokiego poziomu, aby sobie z tym poradzić. (Oczywiście możesz również zbudować własny GC).
www-0av-Com

2
@ user1863152: W takim przypadku przydatny byłby niestandardowy alokator. Nadal nie wymaga zintegrowanej z językiem GC ...
einpoklum

do einpoklum: true. To tylko koń na kursy. Moim wymaganiem było przetwarzanie dynamicznie zmieniających się galonów Informacji o pasażerach transportu. Fascynujący temat. Naprawdę sprowadza się do filozofii oprogramowania.
www-0av-Com

GC, jak odkryli świat Java i .NET, ma w końcu ogromny problem - nie skaluje się. Kiedy masz w pamięci miliardy żywych obiektów, tak jak robimy to obecnie przy użyciu dowolnego nietrywialnego oprogramowania, musisz zacząć pisać kod, aby ukryć rzeczy przed GC. GC w Javie i .NET to obciążenie.
Zach Saw

10

Ideą C ++ było to, że nie zapłacisz żadnego wpływu na wydajność za funkcje, których nie używasz. Tak więc dodanie odśmiecania oznaczałoby, że niektóre programy działałyby bezpośrednio na sprzęcie, tak jak robi to C, a niektóre w ramach maszyny wirtualnej wykonawczej.

Nic nie stoi na przeszkodzie, abyś używał inteligentnych wskaźników powiązanych z mechanizmem wyrzucania elementów bezużytecznych. Wydaje mi się, że Microsoft robił coś takiego z COM i nie poszło to dobrze.


2
Nie sądzę, że GC wymaga maszyny wirtualnej. Kompilator może dodać kod do wszystkich operacji wskaźnika, aby zaktualizować stan globalny, podczas gdy w tle działa osobny wątek, usuwając obiekty w razie potrzeby.
user83255

3
Zgadzam się. Nie potrzebujesz maszyny wirtualnej, ale kiedy tylko zaczynasz mieć coś zarządzającego pamięcią dla ciebie w ten sposób w tle, czuję, że zostawiłeś rzeczywiste „przewody elektryczne” i masz coś w rodzaju sytuacji VM.
Uri


4

Jedną z podstawowych zasad oryginalnego języka C jest to, że pamięć składa się z sekwencji bajtów, a kod musi tylko dbać o to, co te bajty oznaczają w momencie, w którym są używane. Nowoczesne C pozwala kompilatorom na nakładanie dodatkowych ograniczeń, ale C obejmuje - a C ++ zachowuje - zdolność do dekompozycji wskaźnika na sekwencję bajtów, złożenia dowolnej sekwencji bajtów zawierających te same wartości w wskaźnik, a następnie użyj tego wskaźnika do dostęp do wcześniejszego obiektu.

Chociaż umiejętność ta może być przydatna - lub wręcz niezbędna - w niektórych rodzajach aplikacji, język, który obejmuje tę zdolność, będzie bardzo ograniczony w zakresie obsługi dowolnego przydatnego i niezawodnego zbierania śmieci. Jeśli kompilator nie wie wszystkiego, co zostało zrobione za pomocą bitów składających się na wskaźnik, nie będzie miał możliwości dowiedzieć się, czy informacje wystarczające do zrekonstruowania wskaźnika mogą istnieć gdzieś we wszechświecie. Ponieważ możliwe byłoby przechowywanie tych informacji w sposób, do którego komputer nie miałby dostępu, nawet gdyby o nich wiedział (np. Bajty składające się na wskaźnik mogły być wyświetlane na ekranie wystarczająco długo, aby ktoś mógł napisać na kartce papieru), może być dosłownie niemożliwe, aby komputer wiedział, czy wskaźnik może być użyty w przyszłości.

Ciekawym dziwactwem wielu struktur śmieciowych jest to, że odwołanie do obiektu nie jest zdefiniowane przez zawarte w nim wzory bitowe, ale przez związek między bitami przechowywanymi w odwołaniu do obiektu a innymi informacjami przechowywanymi gdzie indziej. W C i C ++, jeśli wzór bitowy przechowywany we wskaźniku identyfikuje obiekt, ten wzór bitowy będzie identyfikował ten obiekt, dopóki obiekt nie zostanie jawnie zniszczony. W typowym systemie GC obiekt może być reprezentowany za pomocą wzorca bitowego 0x1234ABCD w danym momencie, ale następny cykl GC może zastąpić wszystkie odniesienia do 0x1234ABCD odniesieniami do 0x4321BABE, po czym obiekt będzie reprezentowany przez ten ostatni wzorzec. Nawet gdyby wyświetlić wzór bitowy powiązany z odwołaniem do obiektu, a następnie odczytać go z klawiatury,


To naprawdę dobra uwaga, właśnie niedawno ukradłem niektóre bity z moich wskaźników, ponieważ w przeciwnym razie byłyby głupie ilości brakujących pamięci podręcznej.
Przechodzień Do

@PasserBy: Zastanawiam się, ile aplikacji korzystających ze wskaźników 64-bitowych skorzystałoby bardziej na wykorzystaniu skalowanych wskaźników 32-bitowych jako odniesień do obiektów lub utrzymaniu prawie wszystkiego w przestrzeni adresowej 4GiB i użyciu specjalnych obiektów do przechowywania / pobierania danych z dużej -sposób przechowywania poza? Maszyny mają wystarczającą ilość pamięci RAM, aby zużycie pamięci RAM 64-bitowych wskaźników mogło nie mieć znaczenia, z wyjątkiem tego, że pożerają dwukrotnie więcej pamięci podręcznej niż wskaźniki 32-bitowe.
supercat

3

Wszystkie techniczne rozmowy komplikują koncepcję.

Jeśli umieścisz GC w C ++ dla całej pamięci automatycznie, rozważ coś w rodzaju przeglądarki internetowej. Przeglądarka internetowa musi załadować pełny dokument internetowy ORAZ uruchomić skrypty internetowe. Zmienne skryptów internetowych można przechowywać w drzewie dokumentów. W WIELKIM dokumencie w przeglądarce z dużą ilością otwartych kart oznacza to, że za każdym razem, gdy GC musi wykonać pełną kolekcję, musi również zeskanować wszystkie elementy dokumentu.

Na większości komputerów oznacza to, że wystąpią WADY STRONY. Więc głównym powodem odpowiedzi na pytanie jest to, że wystąpią WADY STRONY. Będziesz o tym wiedział, kiedy Twój komputer zacznie uzyskiwać duży dostęp do dysku. Wynika to z faktu, że GC musi dotykać dużej ilości pamięci, aby udowodnić nieprawidłowe wskaźniki. Jeśli masz aplikację działającą w dobrej wierze i korzystającą z dużej ilości pamięci, musisz skanować wszystkie obiekty, każda kolekcja jest siejąca spustoszenie ze względu na WADY STRONY. Błąd strony występuje, gdy pamięć wirtualna musi zostać ponownie odczytana z pamięci RAM do dysku.

Zatem poprawnym rozwiązaniem jest podzielenie aplikacji na części wymagające GC i części, które tego nie potrzebują. W przypadku powyższego przykładu przeglądarki internetowej, jeśli drzewo dokumentu zostało przypisane do malloc, ale javascript działał z GC, to za każdym razem, gdy GC w nim uruchomi, skanuje tylko niewielką część pamięci i wszystkie elementy PAGED OUT pamięci w celu znalezienia drzewo dokumentów nie musi być ponownie stronicowane.

Aby lepiej zrozumieć ten problem, sprawdź pamięć wirtualną i sposób jej implementacji w komputerach. Chodzi o to, że 2 GB jest dostępne dla programu, gdy tak naprawdę nie ma tak dużo pamięci RAM. Na nowoczesnych komputerach z 2 GB pamięci RAM dla systemu 32BIt nie jest to taki problem, pod warunkiem, że działa tylko jeden program.

Jako dodatkowy przykład rozważ pełną kolekcję, która musi śledzić wszystkie obiekty. Najpierw musisz przeskanować wszystkie obiekty osiągalne przez korzenie. Następnie zeskanuj wszystkie obiekty widoczne w kroku 1. Następnie zeskanuj czekające niszczyciele. Następnie przejdź ponownie do wszystkich stron i wyłącz wszystkie niewidoczne obiekty. Oznacza to, że wiele stron może zostać zamienionych i powtórzonych wiele razy.

Krótko mówiąc, moja odpowiedź jest taka, że ​​liczba WAD STRON, które występują w wyniku dotknięcia całej pamięci, powoduje, że pełna GC dla wszystkich obiektów w programie jest niewykonalna, więc programista musi traktować GC jako pomoc dla takich rzeczy jak skrypty i bazy danych, ale rób normalne rzeczy z ręcznym zarządzaniem pamięcią.

Innym bardzo ważnym powodem są oczywiście zmienne globalne. Aby kolektor wiedział, że wskaźnik zmiennej globalnej znajduje się w GC, wymagałoby określonych słów kluczowych, a zatem istniejący kod C ++ nie działałby.


3

KRÓTKA ODPOWIEDŹ: Nie wiemy, jak efektywnie zbierać śmieci (przy niewielkim nakładzie czasu i przestrzeni) i poprawnie przez cały czas (we wszystkich możliwych przypadkach).

DŁUGA ODPOWIEDŹ: Podobnie jak C, C ++ jest językiem systemowym; oznacza to, że jest używany podczas pisania kodu systemowego, np. systemu operacyjnego. Innymi słowy, C ++ jest zaprojektowany, podobnie jak C, z najlepszą możliwą wydajnością jako głównym celem. Standard języka nie dodaje żadnych funkcji, które mogłyby utrudniać osiągnięcie celu wydajności.

To powstrzymuje pytanie: Dlaczego odśmiecanie utrudnia wydajność? Głównym powodem jest to, że jeśli chodzi o implementację, my [informatycy] nie wiemy, jak zrobić wyrzucanie elementów bezużytecznych, we wszystkich przypadkach. Dlatego kompilatorowi C ++ i systemowi wykonawczemu nie jest możliwe wydajne zbieranie śmieci przez cały czas. Z drugiej strony programista C ++ powinien znać swój projekt / implementację i jest najlepszą osobą, która decyduje o tym, jak najlepiej wykonywać wyrzucanie elementów bezużytecznych.

Wreszcie, jeśli kontrola (sprzęt, szczegóły itp.) I wydajność (czas, przestrzeń, moc itp.) Nie są głównymi ograniczeniami, to C ++ nie jest narzędziem do pisania. Inny język może służyć lepiej i oferować więcej [ukrytego] zarządzania środowiskiem wykonawczym, z koniecznym narzutem.


3

Kiedy porównujemy C ++ z Javą, widzimy, że C ++ nie został zaprojektowany z myślą o niejawnym Garbage Collection, podczas gdy Java była.

Posiadanie dowolnych wskaźników w stylu C jest nie tylko złe dla implementacji GC, ale także zniszczyłoby wsteczną kompatybilność dla dużej ilości C ++ - starszego kodu.

Ponadto C ++ jest językiem, który ma działać jako samodzielny plik wykonywalny zamiast mieć złożone środowisko uruchomieniowe.

Podsumowując: Tak, możliwe jest dodanie Garbage Collection do C ++, ale dla zachowania ciągłości lepiej tego nie robić.


1
Zwalnianie pamięci i uruchamianie destruktorów to zbyt osobne problemy. (Java nie ma destruktorów, co jest PITA.) GC zwalnia pamięć, nie uruchamia lekarzy.
ciekawy,

0

Głównie z dwóch powodów:

  1. Ponieważ nie potrzebuje go (IMHO)
  2. Ponieważ jest prawie niezgodny z RAII, który jest kamieniem węgielnym C ++

C ++ oferuje już ręczne zarządzanie pamięcią, alokację stosów, RAII, kontenery, automatyczne wskaźniki, inteligentne wskaźniki ... To powinno wystarczyć. Śmieciarki przeznaczone są dla leniwych programistów, którzy nie chcą spędzać 5 minut na zastanawianiu się, kto powinien posiadać, które obiekty lub kiedy należy uwolnić zasoby. Nie tak robimy rzeczy w C ++.


Istnieje wiele (nowszych) algorytmów, które są z natury trudne do wdrożenia bez wyrzucania elementów bezużytecznych. Czas mijał. Innowacje pochodzą również z nowych informacji, które dobrze pasują do (wysokiego poziomu) języków wysokiego poziomu. Spróbuj przenieść dowolne z nich do GC free C ++, zauważysz nierówności na drodze. (Wiem, że powinienem podać przykłady, ale teraz trochę się spieszę. Przepraszam. Jedno, o czym mogę teraz myśleć, obraca się wokół trwałych struktur danych, w których liczenie referencji nie będzie działać.)
BitTickler,

0

Nakładanie wyrzucania elementów bezużytecznych to tak naprawdę zmiana paradygmatu z niskiego poziomu na wyższy.

Jeśli spojrzysz na sposób, w jaki ciągi są obsługiwane w języku z funkcją wyrzucania elementów bezużytecznych, zauważysz, że umożliwiają TYLKO funkcje manipulacji ciągami na wysokim poziomie i nie pozwalają na binarny dostęp do ciągów. Mówiąc najprościej, wszystkie funkcje łańcucha najpierw sprawdzają wskaźniki, aby zobaczyć, gdzie jest łańcuch, nawet jeśli rysujesz tylko bajt. Więc jeśli wykonujesz pętlę, która przetwarza każdy bajt w ciągu znaków w języku z odśmiecaniem, musi obliczyć lokalizację podstawową plus przesunięcie dla każdej iteracji, ponieważ nie może wiedzieć, kiedy ciąg został przeniesiony. Następnie musisz pomyśleć o stosach, stosach, wątkach itp.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.