Kein shared_ptr?
« | 18 Apr 2020 | »Bisher gab es im GATE Projekt keine Äquivalent zum std::shared_ptr
.
Der Grund dafür war einfach: Alle C-Objekte implementieren ihr eigenes
intrusive Reference-Counting
und die C++ Objekte sind daher nur leichtgewichtige Wrapper.
Damit sind die meisten GATE C++ Objekte bereits eine typisierte Abart
von std::shared_ptr
.
Doch ab nun ist alles anders …
Eine der interessantesten Fähigkeiten von Smart-Pointern
wie std::shared_ptr
ist es Objekte ohne Referenzzählung durch eben eine
solche zu “erweitern”. Genau genommen wird ein Zählerobjekt geschaffen,
das einen Pointer zum Zielobjekt hat.
Wir sprechen hier somit vom non-intrusive reference counting.
Schnittstellen-technisch sind diese Konstrukte also eine Art von Hülle um die eigentlichen Objekte, die auf diese Weise neue Features hinzufügen.
Mit der neuen C-Objekt Klasse der gate_wrapper_t*
existiert nun auch
im GATE Framework eine Struktur, die beliebige Objekt “aufnehmen” und eine
Speicherverwaltung für diese bereitstellen kann.
Und auf der C++ Seite übernimmt das Template gate::Wrapper<T>
die “schönere”
Repräsentation dieses Features.
make_shared nach C portieren
Die noch wichtigere Eigenschaft von std::shared_ptr
ist jedoch die Fähigkeit,
das Datenobjekt und die Referenzzählung in einen Speicherblock zu packen,
und diese Aufgabe übernimmt für gewöhnlich die Funktion std::make_shared
.
Wenn man mit dem “unschönen” Code
1std::shared_ptr<T> my_ptr(new T);
arbeitet, generiert man zwei Probleme:
new T
generiert ein Speicherfragment und der Konstruktor vonstd::shared_ptr
erzeugt ein weiteres, die mit einem Pointer aneinander gekettet werden.
Problem: Speicherfragmentierungnew T
könnte gut gehen, aber demstd::shared_ptr
könnte der freie Speicher ausgehen. Es fliegt eine Exception und schon wäre das vorhergehendenew T
zu einem unauflösbaren Memory-Leak geworden.
make_shared
Erzeugt aber einen übergroßen Puffer in shared_ptr
und
konstruiert eine T
Instanz in diesen Puffer hinein. Damit liegen beide
Objekte hintereinander in einem Speicherblock und reduzieren die
Fragmentierung. Und egal an welcher Stelle eine Exception fliegt, es wird
alles sauber aufgeräumt.
Das gleiche Konzept ist auch mit gate_wrapper_t
möglich, doch nachdem wir
hier tief in der C Welt stecken, bedarf es der Speicherung von
Funktionszeigern zu den entsprechenden Kon- und Destruktoren.
Der C-Teil kann nur flachen Speicher bereitstellen und diesen wieder freigeben. Daher muss sich der C++-Teil darum kümmern das finale Objekt auf den Speicher zu konstruieren. … nun gut, das geht beim Erstellen des Objektes in einem Schritt und findet alles auf der C++ Seite statt.
Doch die Löschung findet im C-Teil statt und somit muss der C++ Teil schon beim Erzeugen des Objektes eine Destruktor-Funktion miterstellen und diese dem C-Teil übergeben, damit sie dieser bei der finalen Löschung aufrufen kann.
Ein Vorteil dieser Lösung - meines Erachtens nach - ist, dass man nun C++ Objekte verwaltet durch viele Programmschichten (in C und anderen Sprachen) weiterreichen kann, um sie am Ende in einem anderen C++ Abschnitt weiterverarbeiten zu können.
Ich hatte am Anfang lange überlegt, ob man diese Hilfskonstrukt nicht als “Carrier” bezeichnen soll und offen gesagt erinnert die API eher an ein “Trägerobjekt”. Doch in der Anwendung und vor allem auf der C++ Seite wirkt die Schnittstelle (danke Templates) eben wie ein SmartPointer wo man ein Objekt über ein anderes aufgerufen wird … und dafür passt dann “Wrapper” besser.
Fazit
Es ist somit quasi das vierte Mal in meinem Leben, dass ich eine von
std::share_ptr
inspirierte SmartPointer Implementierung selbst schreibe.
Und dadurch, dass die GATE Lösung sich nach C abbilden und mit C++ interagieren kann, handelt es sich somit im die mächtigste dieser Implementierungen.
Gleichzeitig muss ich aber eingestehen, dass die Umwege über Funktionszeiger
in der Praxis ein klein bisschen Performance kosten werden. Denn bei einer
reinen C++ Implementierung über Templates hatte der Compiler eine größere
Chance auf Optimierungspotential.
C Funktionszeiger reduzieren diese Möglichkeiten etwas, doch gerade in
meinen Bereichen steht Kompatibilität stets über der Performance.
… und ganz nebenbei … wer weiß ob diese Unterschiede in der Praxis überhaupt messbar sind.
Wie auch immer … ein schöner Tag geht für mich zu Ende. Ich liebe es, sich mit solche Details zu befassen, die den meisten anderen gar nicht erst auffallen.