Prawdziwa odpowiedź jest taka, że jedynym sposobem na stworzenie bezpiecznego, wydajnego mechanizmu wyrzucania elementów bezużytecznych jest obsługa języka dla nieprzejrzystych odniesień. (Lub, odwrotnie, brak wsparcia na poziomie językowym dla bezpośredniej manipulacji pamięcią).
Java i C # mogą to zrobić, ponieważ mają specjalne typy odwołań, których nie można manipulować. Daje to środowisku wykonawczemu swobodę robienia rzeczy, takich jak przenoszenie przydzielonych obiektów w pamięci , co ma kluczowe znaczenie dla wysokowydajnej implementacji GC.
Dla przypomnienia, żadna współczesna implementacja GC nie wykorzystuje zliczania referencji , więc jest to całkowicie czerwony śledź. Współczesne GC korzystają z kolekcji generacyjnej, w której nowe alokacje są traktowane zasadniczo tak samo, jak alokacje stosu w języku takim jak C ++, a następnie okresowo wszelkie nowo alokowane obiekty, które są jeszcze żywe, są przenoszone do osobnej przestrzeni „ocalałych” i całej generacji przedmiotów zostaje natychmiast zwolniony.
Takie podejście ma zalety i wady: zaletą jest to, że przydziały sterty w języku obsługującym GC są tak szybkie, jak przydziały stosu w języku, który nie obsługuje GC, a wadą jest to, że obiekty, które muszą wykonać czyszczenie przed zniszczeniem albo wymagają osobnego mechanizmu (np. using
słowa kluczowego C # ), w przeciwnym razie ich kod czyszczenia będzie działał niedeterministycznie.
Zauważ, że kluczem do wysokowydajnej GC jest obsługa języka dla specjalnej klasy referencji. C nie obsługuje tego języka i nigdy nie będzie; ponieważ C ++ ma przeciążenie operatora, może emulować typ wskaźnika GC, chociaż trzeba to zrobić ostrożnie. W rzeczywistości, gdy Microsoft wynalazł swój dialekt języka C ++, który działałby pod CLR (środowisko uruchomieniowe .NET), musiał wynaleźć nową składnię dla „odniesień w stylu C #” (np. Foo^
), Aby odróżnić je od „odniesień w stylu C ++” (np Foo&
.).
To, co ma C ++ i co jest regularnie używane przez programistów C ++, to inteligentne wskaźniki , które są tak naprawdę tylko mechanizmem liczenia referencji. Nie uważałbym, że liczenie referencji jest „prawdziwym” GC, ale zapewnia wiele takich samych korzyści, kosztem mniejszej wydajności niż ręczne zarządzanie pamięcią lub prawdziwe GC, ale z korzyścią deterministycznego zniszczenia.
Ostatecznie odpowiedź naprawdę sprowadza się do funkcji projektowania języka. C dokonał jednego wyboru, C ++ dokonał wyboru, który umożliwił kompatybilność wsteczną z C, jednocześnie zapewniając alternatywy, które są wystarczająco dobre dla większości celów, a Java i C # dokonały innego wyboru, który jest niezgodny z C, ale jest również wystarczająco dobry dla większość celów. Niestety, nie ma srebrnej kuli, ale znajomość różnych dostępnych opcji pomoże ci wybrać odpowiedni dla dowolnego programu, który obecnie próbujesz zbudować.