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

Databyte

Alter Hase

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

11

04.12.2014, 18:01

In welchem Kontext soll das ganze denn eingesetzt werden? Geht es um Ressourcen-Verwaltung? Dann ist die Frage, was du mit der unique-Ownership erreichen möchtest?
Willst du anderen einfach den Zugriff auf die Ressourcen unterm Hintern wegziehen :thumbsup: ? Also aus welchem Grund überlebt die Referenz auf die Resource den die Ressource?

Evrey

Treue Seele

Beiträge: 245

Beruf: Weltherrscher

  • Private Nachricht senden

12

04.12.2014, 18:09

Der Anwendungs-Fall ist folgender:
owned_ptr/borrowed_ptr ist dann zu verwenden, wenn ein Objekt (z.B. ein Manager) explizit Objekte besitzt, die von anderen benutzt werden dürfen, deren Verwendung allerdings über die Lebenszeit des Besitzers hinweg keinen Sinn ergibt. Das kann z.B. ein OpenAL-Buffer sein, der nur so lange zu verwenden ist, wie der OpenAL-Kontext existiert. Generell bei _allen_ Objekten, zu deren Dokumentation irgendwas wie "Don't use unless this/that..." oder "Don't delete, because..." steht.

unique_ptr ist zu nutzen, wenn _nur_einer_ gleichzeitig ein Objekt verwenden darf, ungeachtet davon, wer sich ums Aufräumen kümmert.

shared_ptr/weak_ptr ist dann zu nutzen, wenn mehrere gleichzeitig ein Objekt verwenden dürfen, aber jeder dieses Objekt zu jeder Zeit benutzen darf.

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:

13

04.12.2014, 18:31

unique_ptr ist zu nutzen, wenn _nur_einer_ gleichzeitig ein Objekt verwenden darf, ungeachtet davon, wer sich ums Aufräumen kümmert.

shared_ptr/weak_ptr ist dann zu nutzen, wenn mehrere gleichzeitig ein Objekt verwenden dürfen, aber jeder dieses Objekt zu jeder Zeit benutzen darf.


So werden sie verwendet - das ist aber nicht richtig: Unique pointer drücken aus, dass nur ein Besitzer existiert. Damit ist klar, wer die Ressource freigibt.
Shared pointer drücken aus, dass es mehr als einen Besitzer geben darf. Damit ist nur klar, dass der letzte die Ressource freigibt.

@Databyte: Anwendungsfall ist folgender: Man betrachte ein 2D RPG mit GameObjects und Dungeons, wobei die GameObjects (z.B. Spieler, Gegner) sich zwischen den Dungeons (z.B. durch Treppen) bewegen dürfen.
  • Ein GameObject soll von genau einem Dungeon besessen werden --> unique ownership --> unique pointer
  • Ein GameObject darf sich zwischen Dungeons bewegen --> ownership wechselt, ist aber noch unique --> std::move
Jetzt kommen z.B. KI-Objekte hinzu, die sich merken, welches GameObjekt sie im Moment angreifen wollen.
  • Ein KI-Objekt verwendet ein GameObject, besitzt es aber nicht --> benötigt irgendeinen non-owning Zeiger
  • Das "anvisierte" GameObjekt kann sterben --> unique pointer geht out of scope und gibt Ressource frei --> KI-Objekt zeigt auf ungültiges GameObject
  • Analog kann es auch das Dungeon verlassen --> merkt man sich, um den Fall davor zu umgehen, den Zeiger auf den unique pointer, hat man nicht mehr den Fokus auf das GameObject

Und wie gesagt: widersprechen shared pointer der unique ownership, weshalb ich sie nicht verwenden möchte.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

14

04.12.2014, 18:41

Darf ich fragen, wieso bei dir ein unique_ptr zerstört wird, bevor alle Leute, die das damit verbundene Objekt benutzen zerstört wurden? Wenn das bei dir der Fall ist, dann solltest du besser dein Design dahingehend fixen, dass das eben nicht mehr passeren kann, denn offenbar liegt der Besitz des Objektes eindeutig an der falschen Stelle... ;)

unique_ptr ist zu nutzen, wenn _nur_einer_ gleichzeitig ein Objekt verwenden darf, ungeachtet davon, wer sich ums Aufräumen kümmert.

shared_ptr/weak_ptr ist dann zu nutzen, wenn mehrere gleichzeitig ein Objekt verwenden dürfen, aber jeder dieses Objekt zu jeder Zeit benutzen darf.[/align]

Nope, wie hier ja schon erklärt wurde, sind Nutzung und Besitz sind zwei grundverschiedene Dinge und Smartpointer rein prinzipiell nur für letzteres zuständig. unique_ptr ist zu nutzen, wenn es genau einen eindeutigen Besitzer für ein Objekt gibt, was mit Abstand den Großteil aller Obekte ausmacht und shared_ptr ist für den extrem seltenen Randfall, dass man geteile Besitzverhältnisse braucht, was man fast nie sollte...

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »dot« (04.12.2014, 18:48)


Databyte

Alter Hase

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

15

04.12.2014, 18:47

Der Anwendungs-Fall ist folgender:
owned_ptr/borrowed_ptr ist dann zu verwenden, wenn ein Objekt (z.B. ein Manager) explizit Objekte besitzt, die von anderen benutzt werden dürfen, deren Verwendung allerdings über die Lebenszeit des Besitzers hinweg keinen Sinn ergibt. Das kann z.B. ein OpenAL-Buffer sein, der nur so lange zu verwenden ist, wie der OpenAL-Kontext existiert. Generell bei _allen_ Objekten, zu deren Dokumentation irgendwas wie "Don't use unless this/that..." oder "Don't delete, because..." steht.


Ok in dem Fall würde ich sagen, dass es keine allgemein gültige Lösung gibt, jedenfalls nicht ohne Annahmen darüber zu machen, wie die Objekte benutzt werden. Das liegt daran, dass ich in diesem Beispiel davon ausgehen muss, dass der Al-Buffer (oder allgemein ein Objekt) jederzeit ungültig werden kann, weil sein Kontext-Objekt ( in diesem Fall der Al-Kontext) jederzeit vernichtet werden kann. Mit jederzeit meine ich wirklich zu jeder Zeit, also auch während ich auf dem Objekt operiere (z.b. beim Multithreading). Ich muss also schonmal irgendwelche Annahmen machen:

1. Das Objekt wird nicht ungültig, nur weil sein Kontext-Objekt verschwindet: Das wäre dann ja ein typischer Fall für den shared_ptr/weak_ptr. Dann hat man halt kurze Zeit ein Zombie-Objekt rumfliegen oder kann es noch weiter Benutzen, weil das Kontext-Objekt halt nicht benötigt wird (z.b bei einem Bild welches im Speicher liegt). Der Fall ist ja hier eher nicht gemeint und eher uninteressant.

2. Das Kontext-Objekt überlebt in jedem Fall alle Zugriffe auf seine Objekte: Der Fall ist auch einfach. Wir benutzen einen unique_ptr im Kontext-Objekt und geben raw pointer weiter (oder ne Kapselung, damit man auf keinen Fall delete aufrufen kann).

3. Das Kontext-Objekt wird vernichtet, während niemand auf seine Objekte zugreift: Das ist schon eine harte Annahme, kann aber in einer Single-Threaded-Umgebung durchaus leicht umzusetzten sein (das Objekt darf sich selbst oder das Kontext-Objekt halt unter keinen Umständen selbst vernichten (z.b. über Callbacks oder so)). In dem Fall könnte man diesen Pointer aus deiner zweiten Variante nehmen + expired-Methode. Oder halt weak_ptr + shared_ptr und im Kontext könnte man dann noch ein assert(ptr.unique()) machen.

Wenn ich keine dieser Annahmen mache, muss ich immer damit rechnen, dass das Objekt noch benutzt wird, während jemand auf es zugreift.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

16

04.12.2014, 18:53

Der Anwendungs-Fall ist folgender:
owned_ptr/borrowed_ptr ist dann zu verwenden, wenn ein Objekt (z.B. ein Manager) explizit Objekte besitzt, die von anderen benutzt werden dürfen, deren Verwendung allerdings über die Lebenszeit des Besitzers hinweg keinen Sinn ergibt. Das kann z.B. ein OpenAL-Buffer sein, der nur so lange zu verwenden ist, wie der OpenAL-Kontext existiert. Generell bei _allen_ Objekten, zu deren Dokumentation irgendwas wie "Don't use unless this/that..." oder "Don't delete, because..." steht.

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... ;)

unique_ptr ist zu nutzen, wenn _nur_einer_ gleichzeitig ein Objekt verwenden darf, ungeachtet davon, wer sich ums Aufräumen kümmert.

shared_ptr/weak_ptr ist dann zu nutzen, wenn mehrere gleichzeitig ein Objekt verwenden dürfen, aber jeder dieses Objekt zu jeder Zeit benutzen darf.

Nope, wie hier ja schon erklärt wurde, sind Nutzung und Besitz zwei grundverschiedene Dinge und Smartpointer rein prinzipiell nur für letzteres zuständig. unique_ptr ist zu nutzen, wenn es genau einen eindeutigen Besitzer für ein Objekt gibt, was mit Abstand den Großteil aller Obekte ausmacht und shared_ptr ist für den extrem seltenen Spezialfall, dass man geteile Besitzverhältnisse braucht, was man fast nie sollte...

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »dot« (04.12.2014, 18:59)


17

04.12.2014, 18:57

Ich habe mal ein paar Tests mittels std::chrono durchgeführt: 100 Objekte erzeugen und 100.000-mal mittels non-owning pointer auf jedes der Elemente zugreifen und einen dummy function call auslösen. Dabei habe ich "shared + weak" vs. "sole + dynamic" vs. "unique + raw" getestet und ein Ergebnis bekommen, was meine Erwartungshaltung bestätigt hat:

Quellcode

1
2
3
shared_ptr & weak_ptr:    3624ms
sole_ptr & dynamic_ptr:   1846ms
unique_ptr & raw pointer: 928ms

(Werte @ Netbook btw)
/EDIT: http://pastebin.com/yqDHR0VE (Pfad für sole_ptr.hpp ist anzupassen)

@dot: Die "Designfehler"-Keule liegt immer griffbereit :D Aber wie gesagt: Das Dungeon besitzt das Objekt und die KIs kennen/verwenden es. Ich sehe an diesem Design beim besten Willen keinen Fehler :P Und shared ownership schließe ich mach wie vor (und in Anbetracht der Testergebnisse auch weiterhin) als Lösung aus.

dot

Supermoderator

Beiträge: 9 757

Wohnort: Graz

  • Private Nachricht senden

18

04.12.2014, 19:10

@dot: Die "Designfehler"-Keule liegt immer griffbereit :D Aber wie gesagt: Das Dungeon besitzt das Objekt und die KIs kennen/verwenden es. Ich sehe an diesem Design beim besten Willen keinen Fehler :P

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...

Und shared ownership schließe ich mach wie vor (und in Anbetracht der Testergebnisse auch weiterhin) als Lösung aus.

Und diese Tests sind auch im Release Build gefahren? Performancemessung eines Debug Build ist leider ein witzloses Unterfangen...
Selbst dann sagen die Werte nicht viel über die praktischen Implikationen aus. In einer realen Anwendung werden die aufgerufenen Funktionen den Großteil der Laufzeit ausmachen, sodass der Overhead des Aufrufs an sich sehr schnell vernachlässigbar wird... ;)

Databyte

Alter Hase

Beiträge: 1 040

Wohnort: Na zu Hause

Beruf: Student (KIT)

  • Private Nachricht senden

19

04.12.2014, 19:26

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.

Beiträge: 1 223

Wohnort: Deutschland Bayern

Beruf: Schüler

  • Private Nachricht senden

20

04.12.2014, 19:31

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.

Außerdem würde ich ggf. auch "std::make_shared" testen. (Warum? Siehe bereits von mir verlinkte Diskussion auf Zfx.)
Dieser Pointer ist außerdem im Gegensatz zu deiner Klasse Threadsicher.

@Evrey
Dein Vorschlag wäre theoretisch vom naiven Speicherverbrauch und wahrscheinlich in den meisten Fällen von der Performance überlegen.
Leider gibt es dadurch ein von den "usable_borrowed_ptr", wie du es nennst, ein "Speicherleck"(nicht ganz im engeren Sinn). Es kann auch nach Ende der Lebensdauer des Objektes nicht aller Speicher freigegeben werden. Die" Zombieobjekte" im Heap können erst freigegeben werden, wenn auch alle Pointer die eigentlich nicht Besitzer sind, zerstört wurden.

EDIT:

Zitat von »Databyte«

Ich denke aber weak_ptr wird in deinem Design auf keinen Fall benötigt.

Eigentlich ist das das ganze was ihn interessiert. Er will doch ein "std::unique_ptr" mit "std::weak_ptr" im Prinzip.

Werbeanzeige