Warum funktioniert std::shared_ptr<void>?

Warum funktioniert std::shared_ptr<void>?

Der Trick ist dieser std::shared_ptr führt Typlöschung durch. Grundsätzlich, wenn ein neuer shared_ptr erstellt wird, wird intern ein deleter gespeichert Funktion (die dem Konstruktor als Argument übergeben werden kann, aber wenn sie nicht vorhanden ist, standardmäßig delete aufruft ). Wenn die shared_ptr zerstört wird, ruft es diese gespeicherte Funktion auf und diese ruft deleter auf .

Eine einfache Skizze der Typlöschung, die mit std::function vereinfacht wird und alle Referenzzählungen und andere Probleme vermeidet, ist hier zu sehen:

template <typename T>
void delete_deleter( void * p ) {
   delete static_cast<T*>(p);
}

template <typename T>
class my_unique_ptr {
  std::function< void (void*) > deleter;
  T * p;
  template <typename U>
  my_unique_ptr( U * p, std::function< void(void*) > deleter = &delete_deleter<U> ) 
     : p(p), deleter(deleter) 
  {}
  ~my_unique_ptr() {
     deleter( p );   
  }
};

int main() {
   my_unique_ptr<void> p( new double ); // deleter == &delete_deleter<double>
}
// ~my_unique_ptr calls delete_deleter<double>(p)

Wenn ein shared_ptr von einem anderen kopiert (oder standardmäßig konstruiert) wird, wird der Deleter herumgereicht, so dass, wenn Sie einen shared_ptr<T> konstruieren ab einem shared_ptr<U> die Informationen darüber, welcher Destruktor aufgerufen werden soll, werden auch in deleter herumgereicht .


shared_ptr<T> logisch[*] hat (mindestens) zwei relevante Datenelemente:

  • ein Zeiger auf das verwaltete Objekt
  • ein Zeiger auf die Deleter-Funktion, die verwendet wird, um sie zu zerstören.

Die Löschfunktion Ihres shared_ptr<Test> , so wie Sie es konstruiert haben, ist das normale für Test , die den Zeiger in Test* konvertiert und delete ist es.

Wenn Sie Ihren shared_ptr<Test> drücken in den Vektor von shared_ptr<void> , beides davon werden kopiert, obwohl der erste in void* umgewandelt wird .

Wenn also das Vektorelement zerstört wird und die letzte Referenz mitnimmt, übergibt es den Zeiger an einen Deleter, der es korrekt zerstört.

Es ist tatsächlich etwas komplizierter, weil shared_ptr kann einen Löscher Funktor nehmen und nicht nur eine Funktion, sodass möglicherweise sogar Daten pro Objekt gespeichert werden müssen, anstatt nur ein Funktionszeiger. Aber für diesen Fall gibt es keine solchen zusätzlichen Daten, es würde ausreichen, nur einen Zeiger auf eine Instanziierung einer Vorlagenfunktion zu speichern, mit einem Vorlagenparameter, der den Typ erfasst, durch den der Zeiger gelöscht werden muss.

[*] logischerweise in dem Sinne, dass es Zugriff auf sie hat - sie sind möglicherweise keine Mitglieder von shared_ptr selbst, sondern anstelle eines Verwaltungsknotens, auf den es zeigt.


Es funktioniert, weil es Type Erasure verwendet.

Grundsätzlich, wenn Sie einen shared_ptr bauen , es übergibt ein zusätzliches Argument (das Sie tatsächlich angeben können, wenn Sie möchten), nämlich den Deleter-Funktor.

Dieser Standard-Funktor akzeptiert als Argument einen Zeiger auf den Typ, den Sie in shared_ptr verwenden , also void wandelt es hier entsprechend in den statischen Typ um, den Sie verwendet haben test hier und ruft den Destruktor für dieses Objekt auf.

Jede ausreichend fortgeschrittene Wissenschaft fühlt sich wie Magie an, nicht wahr?