Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

BlueCobold

Community-Fossil

Beiträge: 10 738

Beruf: Teamleiter Mobile Applikationen & Senior Software Engineer

  • Private Nachricht senden

21

04.12.2014, 19:55

Die Frage ist nicht ob der Zeiger, der die Ressource besitzt ("owning"), expired ist, sondern der Zeiger, der die Ressource lediglich verwendet, aber nicht besitz (non-owning).
Wenn das passieren kann, dann ist unique_ptr ohnehin die falsche Wahl. Für genau solche Fälle ist shared_ptr da. Denn offenbar brauchst Du das Objekt ja doch noch, wenn der Owner es zerstört hat. So ein Fall sollte aber niemals eintreten dürfen bei unique Ownership. Denn wenn jemand anderes das Ding noch referenzieren kann, obwohl der Owner es zerstört hat, liegt genau genommen eigentlich eine shared Ownership vor. Die rein non-owning referenzierenden Objekte sollte nie so lange leben können. Klingt für mich nach einem Design-Fehler.
Teamleiter von Rickety Racquet (ehemals das "Foren-Projekt") und von Marble Theory

Willkommen auf SPPRO, auch dir wird man zu Unity oder zur Unreal-Engine raten, ganz bestimmt.[/Sarkasmus]

Helmut

5x Contest-Sieger

Beiträge: 692

Wohnort: Bielefeld

  • Private Nachricht senden

22

04.12.2014, 23:52

Ich möchte diese explizit eindeutige Ownership nicht teilen, d.h. shared_ptr ist für mich nicht relevant.

Wenn du sicherstellen willst, dass es nur einen Besitzer gibt, kannst du auch einfach von shared_ptr erben und den Kopierkonstruktor (und operator=) deaktivieren. Du kannst aber auch einfach direkt shared_ptr benutzen und ihn einfach niemals kopieren. Aber das was du willst ist definitiv ein shared_ptr. Deine Klasse tut auch nichts anderes, als einen shared_ptr zu simulieren (nur dass sie halt nur einen Besitzer erlaubt). Allerdings hat deine Klasse einen entscheidenen Nachteil:

C-/C++-Quelltext

1
2
3
4
5
6
7
template <class T>
116 class sole_ptr
117         : private std::unique_ptr<T> {
118 
119     private:
120         std::vector<dynamic_ptr<T>*> observers;
121 ...

Die Heapallokationen des std::vector machen die Performance deines Pointers geradezu lächerlich. std::shared_ptr bietet die selbe Funktionalität und benötigt keine einzige Heapallokation, wenn man std::make_shared verwendet. Und Heapallokationen sind hier der Flaschenhals. Wenn du deinen Performacetest um std::make_shared ergänzt oder auch nur den Fall ergänzt, dass mehrere Weak Pointer auf einen Besitzerpointer zeigen können, wird sich das Resultat ändern.

Ich empfehle auch den verlinkten Thread auf ZFX. Sehr lesenswert und beschreibt genau dein Problem.
Sei stets geduldig gegenüber Leuten, die nicht mit dir übereinstimmen. Sie haben ein Recht auf ihren Standpunkt - trotz ihrer lächerlichen Meinung. (F. Hollaender)

23

05.12.2014, 09:36

Leider verrätst du nicht genug über das Problem, das lösen zu müssen du meinst, um hier viel sagen zu können. Ich bin mir allerdings sicher, dass sich ein besseres Design, in welchem dieses Problem von vorn herein nicht auftritt, finden ließe...

Habe ich es in meinem Posten (siehe oben) noch nicht ausreichend erklärt? Oo

Also eine VC-Performance-Analyse sagt, dass die 77,7% der Gesamtlaufzeit des Programms dabei draufgeht weak_ptr::lock aufzurufen. Sonst brauchen alle Aufrufe fast gleich viel Zeit.
Wie erwartet ist das locken der weak_ptr das was am längsten dauert. Ich denke aber weak_ptr wird in deinem Design auf keinen Fall benötigt.

Falsch, ich würde die weak_ptr benötigen! Wenn ich shared_ptr verwenden würde, würde ich diese (zum Erhalt der semantischen unique ownership) zwischen den Dungeons mit std::move bewegen und darauf achten sie nie zu kopieren. Entsprechend würde ich weak_ptr an alle verteilen, die die Ressource verwenden wollen. Verteile ich shared_ptr, mache ich die Verwender explizit zu Besitzern des Objektes - das entspricht nicht dem was ich vorhabe (siehe verlinktes Post von oben!).

Außerdem müssen die Tests außerhalb von einer IDE wie Visual Studio durchgeführt werden. In Visual Studio sind Allokationen nämlich unverhältnismäßig langsam. Faktor 10 habe ich noch im Kopf, ich weiß aber nicht, ob das (noch?) stimmt.

Ich habe keine IDE verwendet sondern direkt auf der Linuxkonsole gearbeitet (s.u.).

Wenn das passieren kann, dann ist unique_ptr ohnehin die falsche Wahl. Für genau solche Fälle ist shared_ptr da. Denn offenbar brauchst Du das Objekt ja doch noch, wenn der Owner es zerstört hat. So ein Fall sollte aber niemals eintreten dürfen bei unique Ownership.

Warum sollte das nie eintreten dürfen?

Denn wenn jemand anderes das Ding noch referenzieren kann, obwohl der Owner es zerstört hat, liegt genau genommen eigentlich eine shared Ownership vor.

Warum liegt shared ownership vor, wenn ich shared usage einer Ressource habe? Die beiden Begriffe haben doch klare Unterschiede hinsichtlich der Verwendung des Zeigers!

Und diese Tests sind auch im Release Build gefahren? Performancemessung eines Debug Build ist leider ein witzloses Unterfangen...

Die Heapallokationen des std::vector machen die Performance deines Pointers geradezu lächerlich.

Wenn du deinen Performacetest um std::make_shared ergänzt oder auch nur den Fall ergänzt, dass mehrere Weak Pointer auf einen Besitzerpointer zeigen können, wird sich das Resultat ändern.


Ich habe mal make_shared eingebaut und, dass es pro shared_ptr mehrere weak_ptr gibt (und analog in den anderen Konzepten). Dazu noch die vorgeschlagene "shared ptr only"-Variante. Nur die Itertionen etc. habe ich global etwas variiert. Die neuen Ergebnisse sind:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
$clang++ -o bench bench.cpp -std=c++11
$ ./bench 
shared_ptr & weak_ptr:    4984ms
shared_ptr only:          1229ms
sole_ptr & dynamic_ptr:   2454ms
unique_ptr & raw pointer: 1187ms
$ clang++ -o bench bench.cpp -std=c++11 -O2
$ ./bench 
shared_ptr & weak_ptr:    651ms
shared_ptr only:          154ms
sole_ptr & dynamic_ptr:   180ms
unique_ptr & raw pointer: 152ms


Klares Ergebnis.. allerdings stört mich immernoch, dass ich deswegen die Ownership teilen muss ...:S Vllt schafft ihr es ja noch mich zu überzeugen ^^
Aber das make_shared hat echt nur einen minimalen Einfluss :P

LG Glocke

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

24

05.12.2014, 14:28

Zitat

Da würd ich nun doch erst nochmal schwer drüber nachdenken, wieso es denn zu dem Zustand kommen können sollte, dass Objekte, die OpenAL Buffer verwenden, den OpenAL Kontext überleben und wofür genau dieser Manager eigentlich gut ist... ;)
Das Beispiel ist jetzt nicht das beste gewesen, aber es kann durchaus Anwendungsfälle geben, in denen das passieren darf. Eine Ressource kann ja zwischenzeitlich wegsterben, aber das hindert einen ja nicht daran, im Falle einer Exception eine neue anzufordern und damit weiter zu arbeiten.

Diese Anwendungsfälle mögen rar sein, und ich bin der Ansicht, dass der Anwendungsfall von Glocke eher ein Fall für ein alternatives Design ist, aber rar ist nicht gleich nie. shared_ptr und weak_ptr haben auch lediglich seltene Anwendungsfälle, vor allem weak_ptr, der eigentlich bloß Zirkelschlüsse aufbrechen soll. Aber für den Fall, dass man sie braucht, gibt es sie dann doch im Standard.

Zitat

Die" Zombieobjekte" im Heap können erst freigegeben werden, wenn auch alle Pointer die eigentlich nicht Besitzer sind, zerstört wurden.
Das ist tatsächlich ein nicht zu unterschätzendes Problem, zumal es während all' dieser Zeit den Destruktor-Thread blockiert. Allerdings ist das dann eher ein Fall von Design-Fehler beim Nutzer. Wenn man sich ein Objekt in dem Sinne nur "mal eben ausleiht", sollte es nicht vorkommen, dass ein usable_borrowed_ptr lange existiert. Immerhin wird man da vorm plötzlichen Wegsterben geschützt. Mit unique_ptr<T> und T* allein gibt es diese Blockade nicht. Stirbt die Ressource also während der Verwendung in einem anderen Thread weg, kommt es im Raw Pointer -Thread plötzlich zu einem Crash oder seltsamen Undefined Behavior, weil man mit gelöschtem Speicher arbeitet. Viel Spaß bei der Fehlersuche. Das funktioniert natürlich auch ohne Threads. Und unique_ptr kann wie gesagt den Fall nicht modellieren, in dem die Ressource zwischenzeitlich sterben darf.


@Perftest:
Wie es so schön heißt: Wer misst, misst Mist. Es ist ein Unterschied, ob du die Performance eines winzigen API-Teils isoliert misst, oder alles zusammen im Kontext deiner spezifischen Software. Das sieht so erstmal beeindruckend aus, aber es ist sehr wahrscheinlich, dass diese "langsamen" Operationen im Gesamtprogramm so selten vorkommen, dass du den "Performance-Verlust" nicht einmal bemerkst. Selbstverständlich bringen shared_ptr und weak_ptr einen Overhead gegenüber unique_ptr und T* mit, sobald irgendwas durch die Gegend kopiert oder locked wird. Aber das bisschen Overhead ist den Gewinn durch Robustheit der Anwendung (selbst bei Multithreading) alle Male wert.

Wie angemerkt, ist owned_ptr dann ratsam, wenn eine Ressource einen speziellen Besitzer hat, aber von mehreren genutzt werden darf, wobei der Besitzer zwischenzeitlich sterben und aufräumen kann. Wenn man die Möglichkeit hat, kann man das Problem umgehen, indem man nicht die Ressource ausleiht, sondern den Besitzer besitzt. Dann ist alles eine herkömmliche shared_ptr/weak_ptr-Kette und es ist gewährleistet, dass - wer auch immer den Besitzer löscht - der Besitzer die Ressource entfernt und niemand sonst. Natürlich setzt das voraus, dass es im Sinne des Programms ist, dass mehrere den Besitzer besitzen.

C-/C++-Quelltext

1
2
3
4
int main(int _argc, char** _argv) noexcept {
  asm volatile("lock cmpxchg8b %eax");
  return 0;
} // ::main
(Dieses kleine Biest vermochte einst x86-Prozessoren lahm zu legen.)

=> Und er blogt unter Hackish.Codes D:

25

06.12.2014, 16:09

Natürlich setzt das voraus, dass es im Sinne des Programms ist, dass mehrere den Besitzer besitzen.

Daher habe ich es jetzt erstmal wie folgt gelöst:
  • Der (zu einem Zeitpunkt eindeutige) Besitzer besitzt die Ressource mittels std::unique_ptr<> und stellt zur Observation/Manipulation Raw-Pointer zur Verfügung
  • Ein Konsument der die Lebenszeit einer verwendeten Ressource möglicherweise überleben könnte, registiert sich beim Besitzer der Ressource und wird über deren Ableben informiert, so dass er den (dann ungültigen) Raw-Pointer entsprechend entfernen / nullen kann.
In Kenntnisnahme/Berücksichtigung eurer "das könnte ein Designfehler sein"-Hinweise werde ich versuche, diese Registierung und Benachrichtigung (erinnert an das Observer Pattern, was ohnehin teilweise etwas umstritten ist) versuchen so gering wie möglich zu halten. Gänzlich vermeiden kann ich es jedoch nicht :S Für Designvorschläge bin ich offen ^^

Zur Erinnerung: Kamera und KI speichern (non-owning) Raw-Pointer auf Objekte, die sie momentan fokussieren. Dabei kann das Objekt von seinem Besitzer aber entfernt werden, so dass die entsprechenden Kameras und KIs informiert werden müssen (soweit bisher).

LG Glocke

Werbeanzeige