Z tego, co widzę, istnieją dwie wszechobecne formy zarządzania zasobami: zniszczenie deterministyczne i wyraźne. Przykładami tego pierwszego byłyby niszczyciele i inteligentne wskaźniki C ++ lub podrzędny DESTROY Perla, a drugim przykładem byłby paradygmat Ruby do zarządzania zasobami lub interfejs IDNET .NET.
Wydaje się, że nowsze języki wybierają to drugie, być może jako efekt uboczny korzystania z systemów wyrzucania elementów bezużytecznych.
Moje pytanie brzmi: biorąc pod uwagę, że destruktory inteligentnych wskaźników lub systemy śmieciowe z liczeniem odniesień - prawie to samo - umożliwiają ukryte i przejrzyste niszczenie zasobów, czy jest to mniej nieszczelna abstrakcja niż niedeterministyczne typy oparte na wyraźnych notacja?
Dam konkretny przykład. Jeśli masz trzy podklasy C ++ jednej nadklasy, jedna może mieć implementację, która nie wymaga żadnego konkretnego zniszczenia. Być może ma swoją magię w inny sposób. Nie wymaga żadnego specjalnego zniszczenia - wszystkie podklasy są nadal używane w ten sam sposób.
Kolejny przykład wykorzystuje bloki Ruby. Dwie podklasy muszą zwolnić zasoby, więc nadklasa wybiera interfejs wykorzystujący blok w konstruktorze, nawet jeśli inne określone podklasy mogą go nie potrzebować, ponieważ nie wymagają specjalnego zniszczenia.
Czy to prawda, że ta ostatnia przecieka szczegóły implementacji zniszczenia zasobów, podczas gdy pierwsza nie.
EDYCJA: Porównując, powiedzmy, Ruby z Perlem może być bardziej sprawiedliwa, ponieważ jedno ma deterministyczne zniszczenie, a drugie nie, ale oba są zbierane w śmieciach.
źródło
(*ptr).Message()
lub równoważnieptr->Message()
. Istnieje nieskończony zestaw dozwolonych wyrażeń, co((*ptr))->Message
jest również równoważne. Ale wszystkie sprowadzają się doexpressionIdentifyingAnObject.Message()
Odpowiedzi:
Twój przykład odpowiada na pytanie. Przejrzyste zniszczenie jest wyraźnie mniej nieszczelne niż zniszczenie jawne. Może przeciekać, ale jest mniej nieszczelny.
Jawne zniszczenie jest analogiczne do malloc / free w C ze wszystkimi pułapkami. Może z odrobiną cukru syntaktycznego, aby wyglądał na oparty na zakresie.
Niektóre zalety przezroczystego niszczenia w porównaniu z jawnym: -
taki sam wzorzec użytkowania -
nie można zapomnieć o uwolnieniu zasobu.
- czyszczenie szczegółów nie zaśmieca krajobrazu w miejscu użytkowania.
źródło
Niepowodzenie w abstrakcji w rzeczywistości nie polega na tym, że zbieranie śmieci jest niedeterministyczne, ale raczej na tym, że obiekty są „zainteresowane” rzeczami, do których się odwołują, i nie są zainteresowane rzeczami, których nie posiadają Bibliografia. Aby zobaczyć, dlaczego, rozważ scenariusz obiektu, który utrzymuje licznik częstotliwości malowania określonej kontrolki. Podczas tworzenia subskrybuje zdarzenie „malowania” formantu, a po usunięciu rezygnuje z subskrypcji. Zdarzenie click po prostu zwiększa pole, a metoda
getTotalClicks()
zwraca wartość tego pola.Kiedy obiekt licznika jest tworzony, musi powodować zapisanie odwołania do siebie w kontrolowanym przez niego kontrolce. Formant naprawdę nie dba o obiekt licznika i byłby równie szczęśliwy, gdyby obiekt licznika i odwołanie do niego przestały istnieć, ale tak długo, jak istnieje odwołanie, będzie wywoływał procedurę obsługi tego obiektu za każdym razem maluje się. Ta akcja jest całkowicie bezużyteczna dla kontroli, ale byłaby przydatna dla każdego, kto kiedykolwiek wezwałby
getTotalClicks()
obiekt.Gdyby np. Metoda utworzyła nowy obiekt „licznika farb”, wykonała jakąś akcję na kontrolerze, zaobserwowała, ile razy odmalowano kontrolkę, a następnie porzuciła obiekt licznika farb, obiekt pozostałby objęty zdarzeniem nawet choć nikomu nie zależy, czy przedmiot i wszystkie odniesienia do niego po prostu znikną. Jednak obiekty nie kwalifikują się do kolekcji, dopóki sama kontrola nie będzie. Gdyby metoda ta była wywoływana wiele tysięcy razy w ciągu życia kontrolki [prawdopodobny scenariusz], mogłaby spowodować przepełnienie pamięci, ale z uwagi na fakt, że koszt N wywołań prawdopodobnie wynosiłby O (N ^ 2) lub O (N ^ 3), chyba że przetwarzanie subskrypcji było bardzo wydajne, a większość operacji nie wymagała żadnego malowania.
Ten konkretny scenariusz można rozwiązać, utrzymując słabe odniesienie do obiektu kontrolnego zamiast obiektu silnego. Model słabej subskrypcji jest pomocny, ale nie działa w ogólnym przypadku. Załóżmy, że zamiast chcieć mieć obiekt, który monitoruje jeden rodzaj zdarzenia z jednego elementu sterującego, chcieliśmy mieć obiekt rejestrujący zdarzenia, który monitoruje kilka elementów sterujących, a mechanizm obsługi zdarzeń w systemie był taki, że każdy element sterujący potrzebował odwołania do innego obiektu rejestrującego zdarzenia. W takim przypadku obiekt łączący kontrolkę z rejestratorem zdarzeń powinien pozostać przy życiu tylko tak długo, jak długo obakontrola jest monitorowana, a rejestrator zdarzeń pozostaje użyteczny. Jeśli ani kontrolka, ani rejestrator zdarzeń nie zawierają silnego odniesienia do zdarzenia łączącego, przestanie istnieć, mimo że nadal jest „przydatne”. Jeśli jedno z nich zawiera silne zdarzenie, żywotność łączącego obiektu może zostać bezużytecznie przedłużona, nawet jeśli drugi umrze.
Jeśli w kosmosie nie ma żadnego odniesienia do obiektu, można go bezpiecznie uznać za bezużyteczny i wyeliminować z istnienia. Fakt, że istnieje odwołanie do obiektu, nie oznacza jednak, że obiekt jest „użyteczny”. W wielu przypadkach faktyczna użyteczność obiektów będzie zależeć od istnienia odniesień do innych obiektów, które - z perspektywy GC - są z nimi całkowicie niezwiązane.
Jeśli obiekty zostaną powiadomione deterministycznie, gdy nikt nie będzie nimi zainteresowany, będą mogli wykorzystać te informacje, aby upewnić się, że każdy, kto skorzysta z tej wiedzy, zostanie o tym poinformowany. W przypadku braku takiego powiadomienia nie ma jednak ogólnego sposobu ustalenia, które obiekty są uważane za „przydatne”, jeśli znany jest tylko zbiór istniejących odniesień, a nie znaczenie semantyczne przypisywane tym odniesieniom. Zatem każdy model, który zakłada, że istnienie lub nieistnienie referencji jest wystarczające do zautomatyzowanego zarządzania zasobami, byłoby skazane na zagładę, nawet gdyby GC mógł natychmiast wykryć porzucenie obiektu.
źródło
Brak „destruktora lub innego interfejsu, który mówi„ ta klasa musi zostać zniszczona ”nie jest umową tego interfejsu. Jeśli stworzysz podtyp, który nie wymaga specjalnego zniszczenia, skłaniam się do uznania, że narusza zasadę podstawienia Liskowa .
Jeśli chodzi o C ++ w porównaniu do innych, nie ma dużej różnicy. C ++ wymusza ten interfejs na wszystkich swoich obiektach. Abstrakcje nie mogą przeciekać, jeśli są wymagane przez język.
źródło
DerivedFooThatRequiresSpecialDestruction
można go utworzyć tylko za pomocą kodu, który wywołujenew DerivedFooThatRequiresSpecialDestruction()
. Z drugiej strony, metoda fabryczna, która zwróciłaDerivedFooThatRequiresSpecialDestruction
kod do kodu, który nie oczekiwał czegoś wymagającego zniszczenia, byłaby naruszeniem LSP.Konieczność ręcznego obserwowania cykli nie jest ani domniemana, ani przejrzysta. Jedynym wyjątkiem jest referencyjny system zliczania z językiem, który zabrania cykli z założenia. Erlang może być przykładem takiego systemu.
Oba podejścia przeciekają. Główną różnicą jest to, że destruktory przeciekają wszędzie w C ++, ale
IDispose
jest bardzo rzadkie w .NET.źródło