Pan Lidström i ja pokłóciliśmy się :)
Pan Lidström twierdzi, że konstrukcja shared_ptr<Base> p(new Derived);
nie wymaga, aby Base miał wirtualnego destruktora:
Armen Tsirunyan : "Naprawdę? Czy shared_ptr wyczyści się poprawnie? Czy mógłbyś w tym przypadku zademonstrować, jak można zaimplementować ten efekt?"
Daniel Lidström : „ Shared_ptr używa własnego destruktora do usunięcia instancji Concrete. W społeczności C ++ jest to znane jako RAII. Moja rada jest taka, że nauczysz się wszystkiego o RAII. Dzięki temu kodowanie w C ++ będzie o wiele łatwiejsze, gdy będziesz używać RAII we wszystkich sytuacjach. "
Armen Tsirunyan : "Wiem o RAII i wiem również, że ostatecznie destruktor shared_ptr może usunąć zapisane piksele, gdy pn osiągnie 0. Ale jeśli px miałby statyczny wskaźnik typu do
Base
i dynamiczny wskaźnik typu doDerived
, to jeśli nieBase
ma wirtualnego destruktora, to spowoduje niezdefiniowane zachowanie. Popraw mnie, jeśli się mylę ”.Daniel Lidström : " Shared_ptr wie, że typ statyczny to Beton. Wie o tym, odkąd przekazałem go w jego konstruktorze! Wygląda trochę jak magia, ale mogę zapewnić, że jest to zgodne z projektem i niezwykle ładne."
Więc osądź nas. Jak jest możliwe (jeśli jest) zaimplementowanie shared_ptr bez wymagania, aby klasy polimorficzne miały wirtualny destruktor? Z góry dziękuję
źródło
shared_ptr<void> p(new Derived)
zniszczy równieżDerived
obiekt przez jego destruktor, niezależnie od tego, czy jest,virtual
czy nie.shared_ptr<T>( (T*)new U() )
gdziestruct U:T
nie zrobisz tego, co właściwe (a można to zrobić pośrednio łatwo, na przykład funkcja, która przyjmuje aT*
i jest przekazywana aU*
)Odpowiedzi:
Tak, w ten sposób można zaimplementować shared_ptr. Boost robi, a standard C ++ 11 również wymaga tego zachowania. Jako dodatkowa elastyczność, shared_ptr zarządza czymś więcej niż tylko licznikiem referencyjnym. Tak zwany deleter jest zwykle umieszczany w tym samym bloku pamięci, który zawiera również liczniki odniesienia. Ale najfajniejsze jest to, że typ tego narzędzia usuwającego nie jest częścią typu shared_ptr. Nazywa się to "wymazywaniem typu" i jest zasadniczo tą samą techniką, która jest używana do implementacji "funkcji polimorficznych" boost :: function lub std :: function do ukrycia aktualnego typu funktora. Aby Twój przykład zadziałał, potrzebujemy konstruktora opartego na szablonach:
template<class T> class shared_ptr { public: ... template<class Y> explicit shared_ptr(Y* p); ... };
Więc jeśli użyjesz tego z klasami Base i Derived ...
class Base {}; class Derived : public Base {}; int main() { shared_ptr<Base> sp (new Derived); }
... konstruktor oparty na szablonie z Y = Derived jest używany do konstruowania obiektu shared_ptr. Konstruktor ma zatem szansę na utworzenie odpowiedniego obiektu deleter i liczników odwołań oraz przechowuje wskaźnik do tego bloku kontrolnego jako element członkowski danych. Jeśli licznik odwołań osiągnie zero, do usunięcia obiektu zostanie użyty wcześniej utworzony i rozpoznający pochodne element usuwający.
Standard C ++ 11 ma do powiedzenia na temat tego konstruktora (20.7.2.2.1):
A dla destruktora (20.7.2.2.2):
(podkreślenie pogrubioną czcionką jest moje).
źródło
the upcoming standard also requires this behaviour
: (a) Którą normę i (b) czy możecie podać odniesienie (do normy)?add a comment
. IMO, to więcejBoost does this
niżthe Standard requires
. Nie sądzę, by standard wymagał tego z tego, co rozumiem. Mówiąc o przykład @sellibitze „sshared_ptr<Base> sp (new Derived);
, Wymaga odconstructor
po prostu poprosić odelete Derived
bycie dobrze określone i dobrze wykształcone. W specyfikacjidestructor
istnieje równieżp
, ale nie sądzę, że odnosi się dop
w specyfikacjiconstructor
.Po utworzeniu shared_ptr przechowuje w sobie obiekt deleter . Ten obiekt jest wywoływany, gdy shared_ptr ma zwolnić wskazany zasób. Ponieważ wiesz, jak zniszczyć zasób w momencie budowy, możesz użyć shared_ptr z niekompletnymi typami. Ktokolwiek utworzył shared_ptr, zapisał tam poprawny plik usuwający.
Na przykład możesz utworzyć niestandardowy plik do usuwania:
void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed. shared_ptr<Base> p(new Derived, DeleteDerived);
p wywoła DeleteDerived, aby zniszczyć wskazany obiekt. Implementacja robi to automatycznie.
źródło
shared_ptr
jako atrybutu.Po prostu,
shared_ptr
używa specjalnej funkcji deleter, która jest tworzona przez konstruktora, który zawsze używa destruktora danego obiektu, a nie destruktora z Base, to trochę pracy z metaprogramowaniem szablonu, ale działa.Coś w tym stylu
template<typename SomeType> shared_ptr(SomeType *p) { this->destroyer = destroyer_function<SomeType>(p); ... }
źródło