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ć.
c++
garbage-collection
c++11
Jason Baker
źródło
źródło
Odpowiedzi:
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:
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ł.
źródło
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.std::shared_ptr
) są cykliczne odwołania, które powodują wyciek pamięci. Dlatego musisz ostrożnie używać,std::weak_ptr
aby 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.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/…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 ilustruje
Martin 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:
The
Mark And Sweep
Jest 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 Counting
jest 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 Counting
Garbage Collectora, który miałby podobną globalną wydajnośćMark And Sweep
bez 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:Chodzi o to, aby właściwie kontrolować żywotność obiektu:
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:
using
konstruuj ... 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ącusing
sł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_ptr
iunique_ptr
wykorzystują 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_ptr
jest to, że emuluje on GC zaimplementowany przez,Reference Counting
ale musisz samodzielnie wdrożyć wykrywanie cyklu ... UrgOczywiście jest coś takiego
weak_ptr
, ale niestety już widziałem przecieki pamięci pomimo użycia zshared_ptr
powodu 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ść:
weak_ptr
Tak więc byłoby wspaniale mieć GC ... jednak nie jest to trywialny problem. A tymczasem musimy tylko podwinąć rękawy.
źródło
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!
źródło
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:
To tylko niektóre z napotkanych problemów.
źródło
Dispose
obiektu 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.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:
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.
źródło
free
cię wezwać (gdzie mam na myślifree
ję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)try (Whatever w=...) {...}
rozwiązuje to (a otrzymasz ostrzeżenie, gdy zapomnisz). Pozostałe są również problematyczne z RAII. Nazywanieclose()
„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.Ź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.
źródło
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:
Jest to niebezpieczne w C ++. Ale w Javie jest to również niebezpieczne! W C ++, jeśli funkcja zwraca wcześniej,
delete
nigdy 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:
Pisanie zajmuje mniej znaków. Nie przeszkadza
new
. Nie wymagaGadget
dwukrotnego pisania . Obiekt jest niszczony na końcu funkcji. Jeśli tego właśnie chcesz, jest to bardzo intuicyjne.Gadget
zachowują się tak samo jakint
lubdouble
. 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_ptr
na przykład za pomocą. (Lub zwracając (duże) obiekty według wartości, wykorzystując semantykę move lubunique_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ą.źródło
Gadget
nie prosi o nic innego w jego imieniu, oryginalny kod byłby całkowicie bezpieczny w Javie, gdybydelete
usunięto bezsensowną instrukcję (do Java) .shared_ptr<T>
specjalnie, gdyT
jest „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. Odpowiednishared_ptr
może być po prostu postrzegany jako wskaźnik GCT
. Ale są w tym ograniczenia i spowolniłoby to wiele programów.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 podczasstring2
Is podczas zapisywaniastring1
będzie zawierał starą lub nową wartość, bez niezdefiniowanego zachowania).shared_ptr<String>
wymaga dużej synchronizacji za kulisami, a przypisanie aString
może zachowywać się dziwnie, jeśli zmienna jest odczytywana i zapisywana jednocześnie. Przypadki, w których ktoś chciałby pisać i czytaćString
jednocześ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ą”.Ponieważ nowoczesny C ++ nie wymaga wyrzucania elementów bezużytecznych.
Odpowiedź Bjarne Stroustrup w tej sprawie brzmi :
Sytuacja w przypadku kodu napisanego w tych dniach (C ++ 17 i zgodnie z oficjalnymi podstawowymi wytycznymi ) jest następująca:
„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:
... 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.
źródło
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.
źródło
Aby odpowiedzieć na większość pytań „dlaczego” na temat C ++, przeczytaj Projektowanie i ewolucja C ++
źródło
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,
źródło
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.
źródło
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.
źródło
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ć.
źródło
Głównie z dwóch powodów:
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 ++.
źródło
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.
źródło