Podstawowe pytanie: kiedy program wywołuje metodę destruktora klasy w C ++? Powiedziano mi, że jest wywoływana za każdym razem, gdy obiekt wychodzi poza zakres lub jest poddawany działaniudelete
Bardziej szczegółowe pytania:
1) Jeśli obiekt jest tworzony za pomocą wskaźnika i ten wskaźnik jest później usuwany lub otrzymuje nowy adres do wskazywania, czy obiekt, na który wskazywał, ma wywołać swój destruktor (zakładając, że nic innego nie wskazuje na niego)?
2) Kontynuując pytanie 1, co definiuje, kiedy obiekt wychodzi poza zakres (nie dotyczy to, kiedy obiekt opuszcza dany {blok}). Innymi słowy, kiedy destruktor jest wywoływany na obiekcie na połączonej liście?
3) Czy kiedykolwiek chciałbyś ręcznie wywołać destruktor?
c++
destructor
Pat Murray
źródło
źródło
Odpowiedzi:
To zależy od rodzaju wskaźników. Na przykład inteligentne wskaźniki często usuwają swoje obiekty po ich usunięciu. Zwykłe wskazówki nie. To samo dotyczy sytuacji, gdy wskaźnik wskazuje inny obiekt. Niektóre inteligentne wskaźniki zniszczą stary obiekt lub zniszczą go, jeśli nie ma już odniesień. Zwykłe wskaźniki nie mają takiej sprytu. Po prostu przechowują adres i umożliwiają wykonywanie operacji na obiektach, na które wskazują, w konkretny sposób.
To zależy od implementacji połączonej listy. Typowe kolekcje niszczą wszystkie zawarte w nich przedmioty, gdy są niszczone.
Tak więc połączona lista wskaźników zwykle zniszczyłaby wskaźniki, ale nie obiekty, na które wskazują. (Co może być poprawne. Mogą to być odniesienia z innych wskaźników). Połączona lista zaprojektowana specjalnie do zawierania wskaźników może jednak spowodować usunięcie obiektów w wyniku własnego zniszczenia.
Połączona lista inteligentnych wskaźników może automatycznie usuwać obiekty, gdy wskaźniki są usuwane, lub robić to, jeśli nie mają więcej odniesień. Wszystko zależy od Ciebie, aby wybrać elementy, które zrobią to, co chcesz.
Pewnie. Jednym z przykładów może być zastąpienie obiektu innym obiektem tego samego typu, ale nie chcesz zwolnić pamięci tylko po to, aby ją ponownie przydzielić. Możesz zniszczyć stary obiekt w miejscu i zbudować nowy na miejscu. (Jednak ogólnie jest to zły pomysł).
źródło
new Foo()
duże „F”.)Foo myfoo("foo")
nie jest to najbardziej irytująca analiza, ale takchar * foo = "foo"; Foo myfoo(foo);
jest.delete myFoo
go wcześniej zadawaćFoo *myFoo = new Foo("foo");
? Albo możesz usunąć nowo utworzony obiekt, nie?myFoo
przedFoo *myFoo = new Foo("foo");
linią. Ta linia tworzy zupełnie nową zmienną o nazwiemyFoo
, przesłaniając każdą istniejącą. Chociaż w tym przypadku nie istnieje, ponieważmyFoo
powyższe znajduje się w zakresieif
, który się zakończył.Inni już zajęli się innymi problemami, więc przyjrzę się tylko jednej kwestii: czy kiedykolwiek chcesz ręcznie usunąć obiekt.
Odpowiedź brzmi tak. @DavidSchwartz podał jeden przykład, ale jest on dość nietypowy. Podam przykład, który jest pod maską tego, z czego wielu programistów C ++ korzysta cały czas:
std::vector
(istd::deque
chociaż nie jest używany tak często).Jak większość ludzi wie,
std::vector
przydzieli większy blok pamięci, gdy / jeśli dodasz więcej elementów, niż może pomieścić obecna alokacja. Jednak gdy to robi, ma blok pamięci, który może pomieścić więcej obiektów niż obecnie znajduje się w wektorze.Aby sobie z tym poradzić, to, co
vector
robi pod osłonami, polega na przydzielaniu pamięci surowej za pośrednictwemAllocator
obiektu (który, o ile nie określono inaczej, oznacza, że używa::operator new
). Następnie, gdy użyjesz (na przykład),push_back
aby dodać element dovector
, wewnętrznie wektor używa a,placement new
aby utworzyć element w (wcześniej) nieużywanej części swojej przestrzeni pamięci.Co się dzieje, gdy / jeśli jesteś
erase
elementem z wektora? Nie może po prostu używaćdelete
- to zwolniłoby cały blok pamięci; musi zniszczyć jeden obiekt w tej pamięci bez niszczenia innych lub zwolnienia żadnego z bloków pamięci, które kontroluje (na przykład, jeśli maszerase
5 elementów z wektora, a następnie natychmiastpush_back
5 więcej elementów, to gwarantuje, że wektor nie zostanie ponownie przydzielony pamięć, kiedy to robisz.Aby to zrobić, wektor bezpośrednio niszczy obiekty w pamięci, jawnie wywołując destruktor, a nie używając
delete
.Jeśli, być może, ktoś inny napisałby kontener przy użyciu ciągłej pamięci, mniej więcej tak, jak
vector
robi (lub jakiś jej wariant, jakstd::deque
naprawdę), prawie na pewno chciałbyś użyć tej samej techniki.Na przykład zastanówmy się, jak możesz napisać kod dla okrągłego bufora pierścieniowego.
W przeciwieństwie do standardowych pojemników, to używa
operator new
ioperator delete
bezpośrednio. W przypadku rzeczywistego użytku prawdopodobnie zechcesz użyć klasy alokatora, ale w tej chwili bardziej rozpraszałoby to niż wnosiło wkład (w każdym razie IMO).źródło
new
, jesteś odpowiedzialny za wywołaniedelete
. Kiedy tworzysz obiekt za pomocąmake_shared
, wynikshared_ptr
jest odpowiedzialny za utrzymywanie licznika i wywoływanie,delete
gdy licznik użycia spadnie do zera.new
(tj. Jest to obiekt stosu).new
.źródło
1) Obiekty nie są tworzone „za pomocą wskaźników”. Istnieje wskaźnik, który jest przypisany do każdego „nowego” obiektu. Zakładając, że to masz na myśli, jeśli wywołasz „delete” na wskaźniku, to faktycznie usunie (i wywoła destruktor na) obiekt, do którego wskaźnik usuwa. Jeśli przypiszesz wskaźnik do innego obiektu, nastąpi przeciek pamięci; nic w C ++ nie zbierze dla Ciebie śmieci.
2) To są dwa oddzielne pytania. Zmienna wychodzi poza zakres, gdy ramka stosu, w której jest zadeklarowana, zostaje zdjęta ze stosu. Zwykle dzieje się tak, gdy opuszczasz blok. Obiekty w stercie nigdy nie wychodzą poza zasięg, chociaż ich wskaźniki na stosie mogą. Nic w szczególności nie gwarantuje, że zostanie wywołany destruktor obiektu z połączonej listy.
3) Niezupełnie. Może istnieć Deep Magic, który sugerowałby coś innego, ale zazwyczaj chcesz dopasować swoje „nowe” słowa kluczowe do słów kluczowych „usuń” i umieścić w destruktorze wszystko, co niezbędne, aby upewnić się, że prawidłowo się wyczyści. Jeśli tego nie zrobisz, pamiętaj, aby skomentować destruktor, podając szczegółowe instrukcje każdemu, kto używa tej klasy, w jaki sposób powinni ręcznie wyczyścić zasoby tego obiektu.
źródło
Aby udzielić szczegółowej odpowiedzi na pytanie 3: tak, są (rzadkie) sytuacje, w których możesz wywołać destruktor jawnie, w szczególności jako odpowiednik nowego miejsca, jak zauważa dasblinkenlight.
Aby podać konkretny przykład:
Celem tego rodzaju rzeczy jest oddzielenie alokacji pamięci od konstrukcji obiektu.
źródło
Wskaźniki - zwykłe wskaźniki nie obsługują RAII. Bez wyraźnego
delete
będzie śmieci. Na szczęście C ++ ma automatyczne wskaźniki, które obsługują to za Ciebie!Zakres - Pomyśl o tym, kiedy zmienna staje się niewidoczna dla twojego programu. Zwykle jest to koniec
{block}
, jak zauważyłeś.Ręczne niszczenie - nigdy tego nie próbuj. Po prostu pozwól, aby zakres i RAII zrobiły za Ciebie magię.
źródło
std::auto_ptr
jest przestarzała w C ++ 11, tak. Jeśli OP faktycznie ma C ++ 11, powinien używać gostd::unique_ptr
dla pojedynczych właścicieli lubstd::shared_ptr
dla wielu właścicieli liczonych jako referencje.std::queue<std::shared_ptr>?
zauważyłem, żepipe()
między wątkiem producenta i konsumenta współbieżność jest o wiele łatwiejsza, jeśli kopiowanie nie jest zbyt drogie.Za każdym razem, gdy używasz słowa „nowy”, czyli dołączasz adres do wskaźnika, lub mówiąc, że zajmujesz miejsce na stercie, musisz go „usunąć”.
1. tak, kiedy coś usuniesz, wywoływany jest destruktor.
2.Wywołanie destruktora listy połączonej powoduje wywołanie destruktora obiektów. Ale jeśli są to wskaźniki, musisz je usunąć ręcznie. 3. gdy miejsce jest określane jako „nowe”.
źródło
Tak, destruktor (inaczej dtor) jest wywoływany, gdy obiekt wychodzi poza zasięg, jeśli znajduje się na stosie lub gdy wywołujesz
delete
wskaźnik do obiektu.Jeśli wskaźnik zostanie usunięty za pośrednictwem,
delete
zostanie wywołany dtor. Jeśli zmienisz przypisanie wskaźnika bez wcześniejszego wywołaniadelete
, otrzymasz wyciek pamięci, ponieważ obiekt nadal istnieje gdzieś w pamięci. W tym drugim przypadku dtor nie jest wywoływany.Dobra implementacja listy połączonej wywoła dtor wszystkich obiektów na liście, gdy lista jest niszczona (ponieważ albo wywołałeś jakąś metodę, aby ją zniszczyć, albo sama wyszła poza zakres). Zależy to od implementacji.
Wątpię, ale nie zdziwiłbym się, gdyby zaszły jakieś dziwne okoliczności.
źródło
Jeśli obiekt jest tworzony nie za pomocą wskaźnika (na przykład A a1 = A ();), destruktor jest wywoływany po zniszczeniu obiektu, zawsze wtedy, gdy funkcja, na której leży obiekt, jest zakończona. Na przykład:
Destruktor jest wywoływany, gdy kod jest wykonywany w wierszu „finish”.
Jeśli obiekt jest tworzony za pomocą wskaźnika (na przykład A * a2 = new A ();), destruktor jest wywoływany po usunięciu wskaźnika (delete a2;). Jeśli punkt nie zostanie usunięty przez użytkownika jawnie lub otrzymany nowy adres przed jego usunięciem, wystąpił wyciek pamięci. To jest błąd.
Na połączonej liście, jeśli używamy std :: list <>, nie musimy przejmować się desktruktorem lub wyciekiem pamięci, ponieważ std :: list <> zakończyło to wszystko za nas. Na utworzonej przez nas połączonej liście powinniśmy napisać desktruktor i jawnie usunąć wskaźnik, w przeciwnym razie spowoduje to wyciek pamięci.
Rzadko nazywamy destruktor ręcznie. Jest to funkcja zapewniająca system.
Przepraszam za mój słaby angielski!
źródło
Pamiętaj, że Konstruktor obiektu jest wywoływany natychmiast po przydzieleniu pamięci dla tego obiektu, a destruktor jest wywoływany tuż przed zwolnieniem pamięci tego obiektu.
źródło