Ich habe Code gefunden, der std :: shared_ptr verwendet, um beim Herunterfahren eine beliebige Bereinigung durchzuführen. Zuerst dachte ich, dieser Code könnte unmöglich funktionieren, aber dann habe ich Folgendes versucht:
#include <memory>
#include <iostream>
#include <vector>
class test {
public:
test() {
std::cout << "Test created" << std::endl;
}
~test() {
std::cout << "Test destroyed" << std::endl;
}
};
int main() {
std::cout << "At begin of main.\ncreating std::vector<std::shared_ptr<void>>"
<< std::endl;
std::vector<std::shared_ptr<void>> v;
{
std::cout << "Creating test" << std::endl;
v.push_back( std::shared_ptr<test>( new test() ) );
std::cout << "Leaving scope" << std::endl;
}
std::cout << "Leaving main" << std::endl;
return 0;
}
Dieses Programm gibt die Ausgabe:
At begin of main.
creating std::vector<std::shared_ptr<void>>
Creating test
Test created
Leaving scope
Leaving main
Test destroyed
Ich habe einige Ideen, warum dies funktionieren könnte, die mit den Interna von std :: shared_ptrs zu tun haben, wie sie für G ++ implementiert sind. Da diese Objekte den internen Zeiger zusammen mit dem Zähler umschließen std::shared_ptr<test>
, std::shared_ptr<void>
behindert die Umwandlung von bis wahrscheinlich den Aufruf des Destruktors nicht. Ist diese Annahme richtig?
Und natürlich die viel wichtigere Frage: Funktioniert dies garantiert standardmäßig oder können weitere Änderungen an den Interna von std :: shared_ptr vorgenommen werden, wenn andere Implementierungen diesen Code tatsächlich beschädigen?
quelle
Antworten:
Der Trick besteht darin, dass
std::shared_ptr
das Löschen des Typs durchgeführt wird. Grundsätzlich wird beim Erstellen einer neuen Funktionshared_ptr
intern einedeleter
Funktion gespeichert (die dem Konstruktor als Argument übergeben werden kann, wenn sie nicht vorhanden ist, wird standardmäßig aufgerufendelete
). Wenn dasshared_ptr
zerstört wird, ruft es diese gespeicherte Funktion auf und das ruft das aufdeleter
.Eine einfache Skizze des Typlöschens, der mit std :: function vereinfacht wird und alle Referenzzählungen und andere Probleme vermeidet, finden Sie hier:
Wenn a
shared_ptr
von einem anderen kopiert (oder standardmäßig erstellt) wird, wird der Deleter herumgereicht, sodass beim Erstellen von ashared_ptr<T>
aus einemshared_ptr<U>
die Informationen darüber, welcher Destruktor aufgerufen werden soll, auch im übergeben werdendeleter
.quelle
my_shared
. Ich würde das beheben, habe aber noch kein Privileg zum Bearbeiten.std::shared_ptr<void>
vermeiden, dass eine nutzlose Wrapper-Klasse deklariert wird, damit ich sie von einer bestimmten Basisklasse erben kann.my_unique_ptr
. Wenn inmain
der Vorlage mitdouble
dem richtigen Deleter instanziiert wird , ist dies jedoch nicht Teil des Typs vonmy_unique_ptr
und kann nicht aus dem Objekt abgerufen werden. Der Typ des Löschers wird aus dem Objekt gelöscht , wenn eine Funktion einemy_unique_ptr
(z. B. durch rWertreferenz) empfängt , weiß diese Funktion nicht und muss nicht wissen, was der Löscher ist.shared_ptr<T>
logischerweise hat [*] (mindestens) zwei relevante Datenelemente:Die Deleter-Funktion von your
shared_ptr<Test>
ist in Anbetracht der Art und Weise, wie Sie sie erstellt haben, die normale fürTest
, die den Zeiger inTest*
unddelete
s it konvertiert .Wenn Sie Ihre
shared_ptr<Test>
in den Vektor von schiebenshared_ptr<void>
, werden beide kopiert, obwohl der erste in konvertiert wirdvoid*
.Wenn das Vektorelement unter Verwendung der letzten Referenz zerstört wird, übergibt es den Zeiger an einen Löscher, der es korrekt zerstört.
Es ist eigentlich etwas komplizierter als das, weil
shared_ptr
es einen Deleter- Funktor nehmen kann anstelle einer Funktion verwenden kann, sodass möglicherweise sogar pro Objekt Daten gespeichert werden und nicht nur ein Funktionszeiger. In diesem Fall gibt es jedoch keine solchen zusätzlichen Daten. Es würde ausreichen, nur einen Zeiger auf eine Instanziierung einer Vorlagenfunktion mit einem Vorlagenparameter zu speichern, der den Typ erfasst, über den der Zeiger gelöscht werden muss.[*] logisch in dem Sinne, dass es Zugriff auf sie hat - sie sind möglicherweise keine Mitglieder des shared_ptr selbst, sondern anstelle eines Verwaltungsknotens, auf den es verweist.
quelle
shared_ptr
direkt mit dem entsprechenden Typ erstellen oder verwendenmake_shared
. Trotzdem ist es eine gute Idee, da sich der Typ des Zeigers von der Konstruktion bis zur Speicherung imshared_ptr
: ändern kann.base *p = new derived; shared_ptr<base> sp(p);
Soweitshared_ptr
es das Objektbase
nicht betrifftderived
, benötigen Sie einen virtuellen Destruktor. Dieses Muster kann beispielsweise bei Fabrikmustern üblich sein.Es funktioniert, weil es Typlöschung verwendet.
Grundsätzlich wird beim Erstellen von a
shared_ptr
ein zusätzliches Argument übergeben (das Sie auf Wunsch tatsächlich angeben können), nämlich der Deleter-Funktor.Dieser Standardfunktor akzeptiert als Argument einen Zeiger auf den Typ, den Sie in verwenden
shared_ptr
, undvoid
wandelt ihn hier entsprechend dem von Ihnen verwendeten statischen Typ umtest
hier, und ruft den destructor auf diesem Objekt.Jede ausreichend fortgeschrittene Wissenschaft fühlt sich wie Magie an, nicht wahr?
quelle
Der Konstruktor
shared_ptr<T>(Y *p)
scheint tatsächlichshared_ptr<T>(Y *p, D d)
wo anzurufend
ein automatisch generierter Deleter für das Objekt ist.In diesem Fall ist der Typ des Objekts
Y
bekannt, sodass der Löscher für diesesshared_ptr
Objekt weiß, welcher Destruktor aufgerufen werden soll, und diese Informationen gehen nicht verloren, wenn der Zeiger in einem Vektor von gespeichert istshared_ptr<void>
.In der Tat verlangen die Spezifikationen, dass ein empfangendes
shared_ptr<T>
Objekt, um ein Objekt zu akzeptierenshared_ptr<U>
, wahr seinU*
muss und implizit in a konvertierbar sein muss,T*
und dies ist sicherlich der Fall,T=void
da jeder Zeigervoid*
implizit in ein konvertiert werden kann . Über den Deleter, der ungültig sein wird, wird nichts gesagt, so dass die Spezifikationen tatsächlich vorschreiben, dass dies korrekt funktioniert.Technisch gesehen enthält IIRC a
shared_ptr<T>
einen Zeiger auf ein verstecktes Objekt, das den Referenzzähler enthält, und einen Zeiger auf das tatsächliche Objekt. Durch Speichern des Deleters in dieser verborgenen Struktur ist es möglich, dass diese scheinbar magische Funktion funktioniert, während sie immer nochshared_ptr<T>
so groß wie ein normaler Zeiger bleibt (die Dereferenzierung des Zeigers erfordert jedoch eine doppelte Indirektionquelle
Test*
ist implizit konvertierbar invoid*
, ist dahershared_ptr<Test>
implizit konvertierbar inshared_ptr<void>
, aus dem Speicher. Dies funktioniert, dashared_ptr
die Zerstörung zur Laufzeit und nicht zur Kompilierungszeit gesteuert werden soll. Sie verwenden intern die Vererbung, um den entsprechenden Destruktor wie zur Zuweisungszeit aufzurufen.quelle
Ich werde diese Frage (2 Jahre später) mit einer sehr vereinfachten Implementierung von shared_ptr beantworten, die der Benutzer verstehen wird.
Zuerst gehe ich zu einigen Nebenklassen, shared_ptr_base, sp_counted_base sp_counted_impl und checked_deleter, von denen die letzte eine Vorlage ist.
Jetzt werde ich zwei "freie" Funktionen namens make_sp_counted_impl erstellen, die einen Zeiger auf eine neu erstellte zurückgeben.
Ok, diese beiden Funktionen sind wichtig für das, was als nächstes passiert, wenn Sie einen shared_ptr über eine Vorlagenfunktion erstellen.
Beachten Sie, was oben passiert, wenn T nichtig ist und U Ihre "Test" -Klasse ist. Es wird make_sp_counted_impl () mit einem Zeiger auf U und nicht mit einem Zeiger auf T aufgerufen. Die Verwaltung der Zerstörung erfolgt hier. Die Klasse shared_ptr_base verwaltet die Referenzzählung in Bezug auf Kopieren und Zuweisen usw. Die Klasse shared_ptr selbst verwaltet die typsichere Verwendung von Operatorüberladungen (->, * usw.).
Obwohl Sie ein shared_ptr zum Leeren haben, verwalten Sie darunter einen Zeiger des Typs, den Sie an new übergeben haben. Beachten Sie, dass wenn Sie Ihren Zeiger in eine Leere * konvertieren, bevor Sie ihn in shared_ptr einfügen, er nicht auf dem checked_delete kompiliert werden kann, sodass Sie auch dort tatsächlich sicher sind.
quelle