Smartpointer übergeben
-
Hallo zusammen,
ist es korrekt einen Smartpointer so zu übergeben?
void addUseRule(std::shared_ptr<ItemUseRule>& rule);oder eher so?
void addUseRule(const std::shared_ptr<ItemUseRule>& rule);oder so?
void addUseRule(std::shared_ptr<ItemUseRule> const& rule);Aktuelle nutze ich die dritte Variante, aber ehrlich gesagt machen mich diese Unterschiedlichen Varianten mit/ohne const total verrückt. Ich glaub das verstehe ich nie

Rückwärts sieht es aktuell so aus:
const std::shared_ptr<ItemUseRule>& getRule() const { return rule; };Ich interpretiere das alles jetzt ungefähr so:
Ich erzeuge außerhalb der Klasse einen shared_pointer und übergebe eine Referenz darauf an die Klasse. Diese speichert die Referenz (?) in einer privaten Variablen
std::shared_ptr<ItemUseRule> rule;.
Beim Aufruf erhalte ich dann wieder eine Referenz auf den am Anfang erzeugten shared_pointer zurück. Ist das richtig? Dann gäbe es ja letztlich nur einen tatsächlich erzeugten shared_ptr, oder?Irgendwann möchte ich allerdings mehrere "Rules" innerhalb der Klasse z.B. in einer
std::map<std::shared_ptr<ItemUseRule>> rule;speichern. In einem meiner letzten Posts habe ich ja gelernt, ich dann mit Referenzen in die Hölle komme. Ich glaub ich bekomme Kopfschmerzen

Ich hoffe es gibt da draußen jemanden, der mir das erklären kann?
Danke dafür,
temi
-
temi schrieb:
Hallo zusammen,
ist es korrekt einen Smartpointer so zu übergeben?
/* snip */es kommt drauf an, was du tun willst.
deine fragen beziehen sich alle *nicht* auf smart-pointer, sondern allgemein auf die frage nach wann du referenzen verwenden kannst/sollst. dass es smart-pointer sind, die du per referenz übergibst, macht das ganze nur unnötig kompliziert.
das:
void foo (X& x);deklariert eine funktion, die ein objekt übernimmt und von der man erwartet, das sie es modifieziert.
das:
void foo (X const& x); void foo (const X& x);bedeutet exakt! dasselbe. manche leute bevorzugen erste variante, weil man dann leicht von links-nach-rechts lesen kann ("x ist eine referenz auf ein const X") und diese lesart auch mit zeigern und komplexeren konstrukten funktioniert.
zweite variante ist allerdings die üblichere und wird auch empfohlen.in diesem fall bedeutet
const, dass die funktionfoodir versichert, dass sie das argument nicht ändern wird. eine alternative dazu wäre:void foo (X x);das bedeutet soviel wie: foo legt -lokal- eine kopie von dem argument an, mit dem foo aufgerufen wird. in diesem fall ist auch klar, dass das ursprüngliche objekt durch die funktion nicht geändert werden kann. diese variante ist die erste variante, die du verwenden solltest, wenn du dir nicht sicher bist, was du verwenden musst, da heutzutage in vielen fällen, wo früher eine kopie angelegt wurde (was nicht immer besondern performant ist), keine mehr angelegt wird.
im falle von
shared_ptrist kann man dir nicht genau sagen, ob ein call-by-value (letztere variante) oder ein call-by-reference besser ist; manche glaskugel würde wohl sagen, du solltest in deinem fallunique_ptrstattshared_ptrverwenden, in kombination mit call-by-value. aber glaskugeln können sich auch mal irren.[quote]
Ich erzeuge außerhalb der Klasse einen shared_pointer und übergebe eine Referenz darauf an die Klasse.Diese speichert die Referenz (?) in einer privaten Variablen [code="cpp"]std::shared_ptr<ItemUseRule> rule;[/code].das ist natürlich keine referenz. das fragezeichen ist unnötig, denn du weißt ja, dass eine referenz so zu deklarieren wäre:
std::shared_ptr<ItemUseRule> & rule;in wahrheit übergibst du der funktion einen
shared_ptrper referenz und die funktion erzeugt eine kopie davon, die sie in ihrer privaten variablen ablegt. hört sich wirklich immer mehr nachunique_ptran, fängt aber langsam an so auszusehen, als würde ein gewöhnlicher zeiger auch reichen.Beim Aufruf erhalte ich dann wieder eine Referenz auf den am Anfang erzeugten shared_pointer zurück. Ist das richtig? Dann gäbe es ja letztlich nur einen tatsächlich erzeugten shared_ptr, oder?
das erübrigt sich dann hoffentlich, denn jetzt weißt du, dass du zwei
shared_ptrerzeugt hast. beide zeigen halt auf dasselbe objekt.Ich hoffe es gibt da draußen jemanden, der mir das erklären kann?
guidelines für parameter-passing:
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-functionseine antwort auf - mehr oder weniger *genau* deine frage: https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/
-
Willst du Referenzen überhaupt sharen? Was willst du eigentlich ausdrücken? Wem soll was gehören? Ich vermute, dass du eigentlich ItemUseRule*, also einen bare-pointer, oder aber einen unique_ptr übergeben willst, sofern die Ownership transferiert werden soll.
In den meisten Fällen will man den Owner gar nicht sharen. Stattdessen hat man einen unique_ptr<T> und ansonsten nur non-owning T*-Zeiger.
Vielleicht helfen dir diese drei GotW-Artikel:
https://herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/
https://herbsutter.com/2013/05/30/gotw-90-solution-factories/
https://herbsutter.com/2013/06/05/gotw-91-solution-smart-pointer-parameters/
-
wow, derselbe Link von dove und mir: 2 dumme, ein Gedanke

-
sutter

-
Danke schon mal für die ausführlichen Erläuterungen und die Links, irgendwann raffe ich es auch noch. :p
In einem anderen Thema hatte ich ja schon mal Referenzen auf Objekte als Argumente übergeben und in einer Liste gespeichert. Als Tipp hatte ich erhalten, dass dies nicht so eine gute Idee ist und Zeiger die bessere Wahl wären. Und jetzt scheiden sich möglicherweise die Geister, die einen sagen am besten nur noch smartpointer verwenden, um den möglichen Problemen mit Pointern aus dem Weg zu gehen. Ich habe auch schon gelesen, Smartpointer nur zu verwenden, wenn es erforderlich ist. Dann bin ich über eine Diskussion gestolpert, ob es besser ist Smartpointer als Referenz zu übergeben oder nicht.
Aktuell möchte ich Objekte in einem anderen Objektes in einer Liste speichern. Als "Anwendung" habe ich mir jetzt mal eine Art von Textadventure herausgesucht, d.h. es gibt ein Item (das ist ein Gegenstand im Spiel) ein Item kann keine, eine oder mehrere ItemUseRules beinhalten (die Liste). Eine ItemUseRule ist ein Wrapperobjekt das ein weiteres Item (mit dem kombiniert werden kann) und ein Ergebnisitem (das dabei rauskommt) enthält.
Rufe ich ein Item->useWith(einAnderesItem) auf, wird die Liste mit den Regeln durchlaufen und geschaut, ob es eine Regel für dieses Item gibt und im Fall eines Erfolgs wird das Ergebnis zurück geliefert.
Ungefähr: Benutze "Rostiges Messer" mit "Schleifpapier" => "Scharfes Messer"
Ob das jetzt ein geiles Konzept ist oder ob das anders besser zu lösen ist, darum geht es erst mal nicht. Es geht darum etwas halbwegs sinnvolles zu erstellen und dabei auch ein paar Konzepte zu lernen. Außerdem gefallen mit Adventures

Später soll ein "Gameobjekt" eine Liste von "Locations" enthalten. Die Locations wiederum enthalten "Items", "Persons" und "Exits". Über die Exits mit "ExitRules" gelangt man in andere Locations usw. Personen können auch selbst wieder Items enthalten.
Noch mal zurück zu den Fragen: Sind Smartpointer (unique oder shared) die richtige Wahl? Und wenn, übergebe ich dann doch besser den Smartpointer by value?
Danke für die Hilfe,
temi
-
@wob & dove: Der Link ist wirklich sehr interessant und ich hoffe tatsächlich dadurch viele Fragen beantwortet zu bekommen. Heute abend lese ich weiter...
Nur eine kleine Frage vorher:
Ein Smartpointer ist ja ein Wrapper um ein anderes Objekt, bzw. Zeiger auf ein Objekt.
Übergebe ich eine Smartpointer const dann verhindere ich damit doch nur, dass der Wrapper verändert wird, nicht das Objekt auf das er zeigt, oder?
-
richtig
Ein Smartpointer ist ja ein Wrapper um ein
anderes Objekt, bzw.Zeiger auf ein Objekt.Er Unterstützt dich bei der Ressourcenverwaltung, (freigeben) .
Wichtiger Punkt bei der Ressourcenverwaltung ist halt "membership"Übergebe ich eine Smartpointer const dann verhindere ich damit doch nur, dass der Wrapper verändert wird, nicht das Objekt auf das er zeigt, oder?
richtig.
In der Konsequenz verhinderst du damit das resetten und releasen des Pointers.Const pointer wrappen (in bezug auf eigentümerschaft) macht in der Praxis aber selten sinn.
Wenn du jemanden einen const pointer zum arbeiten gibst, dann überträgst du ihm meistens auch nicht die eigentuemerschaft auf den pointer.
Deshalb langt da auch der wrapper freie pointer (const Myclass * p) als parameter.
Übergibts du die eigentuemerschaft, muss der neue besitzer wiederum in der lage sein die ressource freizugeben (aka den pointer zu deleten), was meist mit nem const pointer nicht geht ...Deshalb:
- Eigentümershaft (bzw sharing) bedingt die nicht const variante.
- const pointer brauchen den Wrapper (und das wissen ueber den Wrapper) nicht - const pointer als parameter langt.Gibt natuerlich ausnahmen, aber hoffentlich wenige ... ^^
-
temi schrieb:
Übergebe ich eine Smartpointer const dann verhindere ich damit doch nur, dass der Wrapper verändert wird, nicht das Objekt auf das er zeigt, oder?
Probiere es doch mal aus. Kannst ja mal testen, ob folgendes bei dir compiliert oder ob es einen Fehler bzgl. const gibt.
#include <memory> int main() { const auto cuptr = std::make_unique<int>(42); *cuptr = 23; }In dem auch schon irgendwo von mir verlinkten Video von Herb Sutter über Leak-Freedom wird ein
const unique_ptrz.B. für Pimpl vorgeschlagen. Da wäre es ja irgendwie kontraproduktiv, wenn man dann das ensprechende Impl-Objekt nicht mehr ändern könnte.
-
So früh am morgen und ich schreibe schon wieder Quatsch. Vergiss meinen letzten Absatz, der ergibt keinen Sinn. Dann könnte man ein Objekt ja nicht mehr moven. Der const unique_ptr ist für ein Array fixer Größe sinnvoll.
Erst nachdenken, dann schreiben. Bin noch im Trump-Modus: erst schreiben, dann nachdenken

-
This is the preferred way to express a widget-consuming function, also known as a “sink.”
// Smelly 20th-century alternative void bad_sink( widget* p ); // will destroy p;Das verstehe ich nicht, wieso wird p zerstört?
---
Nehmen wir mal an, bezogen auf meine Ausführungen weiter oben, ich hätte eine Klasse "ItemCatalog" in der alle verwendeten "Items" gespeichert und abgerufen werden können. Rufe ich die Funktion
neuesItem = einItem->useWith(einAnderesItem), dann wäre das ja eine Funktion die die Informationen von "einAnderesItem" benötigt, aber nicht verändern darf/muss und wiederum ein Item zurück liefert. Das wäre doch ein Fall von:
shared_ptr<item>& useWith(shared_ptr<item>& einItem);Andererseits kann es ja sein, dass die Kombination von zwei Items nicht möglich ist und die Rückgabe demnach ein "nullptr" ist. Also muss es wohl eher:
item* useWith(item* einItem);lauten.
Mindestens für den Rückgabewert.Habe ich das jetzt einigermaßen richtig verstanden?
In dem Fall wären meine im "ItemCatalog" gespeicherten Items wohl eher unique_ptr<Item>. Damit ist der "ItemCatalog" Owner und übernimmt die Verwaltung der Items. Wenn ich ein Item abrufe dann etwa so:
item* get(int id) // jedes Item hat eine Id, nehmen wir mal so an { return items[id]->get(); // items ist z.B. eine map<int, unique_ptr<Item>> }Danke für die Unterstützung meiner Schwerfälligkeit,
temiEdit: Statt items[id].get() => items[id]**->**get()
-
Noch als Ergänzung zum vorherigen Beitrag.
Wenn ein Item eine Id hat, dann wäre es natürlich auch möglich jedem Item, z.B. im Konstruktor einen Verweis zum "ItemCatalog" mitzugeben und alles andere über die Id zu erledigen.
//Item.h int useWith(int itemId)Die benötigten Infos können dann vom Item selbst aus dem Katalog abgerufen werden.
Da ich aber lernen möchte, will ich auch die andere Variante verstehen.
Gruß,
temi
-
temi schrieb:
This is the preferred way to express a widget-consuming function, also known as a “sink.”
// Smelly 20th-century alternative void bad_sink( widget* p ); // will destroy p;Das verstehe ich nicht, wieso wird p zerstört?
Steht doch da: nämlich im Kommentar! Von außen sieht man es der Funktion nicht an. Im Kommentar steht "will destroy p", also muss man sich darauf verlassen, dass irgendwo in der Implementierung von bad_sink ein "delete p" stehen wird.
Das stinkt, weil man es der Signatur gerade NICHT ansehen kann und nur aus dem Kommentar (sofern überhaupt vorhanden) erfährt. Genau deswegen ist ein modernes Sink (unique_ptr by value) klarer: man sieht sofort an der Signatur, dass Ownership des Objektes an die Funktion transferiert wird und man braucht keinen Kommentar mehr.
-
wob schrieb:
temi schrieb:
This is the preferred way to express a widget-consuming function, also known as a “sink.”
// Smelly 20th-century alternative void bad_sink( widget* p ); // will destroy p;Das verstehe ich nicht, wieso wird p zerstört?
Steht doch da: nämlich im Kommentar! Von außen sieht man es der Funktion nicht an. Im Kommentar steht "will destroy p", also muss man sich darauf verlassen, dass irgendwo in der Implementierung von bad_sink ein "delete p" stehen wird.
Ach so. Der Kommentar ist für den Aufrufer der Funktion gedacht, um darauf hinzuweisen, dass die Funktion den Zeiger freigeben wird. Ich hatte das anders interpretiert.