Smartpointer und move richtig verwendet?
-
Ich Frage mich, ob ich bei dem unteren Beispiel das Prinzip von smartpointern und move richtig verstanden habe.
class Animal; class Cage { private: std::shared_ptr<Animal> m_animal; public: Cage (std::shared_ptr<Animal> animal) : m_animal(std::move(animal)) { //do stuff with animal } ~Cage() { //do stuff with animal } }; class Collar { void Scan(); } class Cat: public Animal { Collar collar; } class CatCage : public Cage { private: std::shared_ptr<Cat> m_cat; public: CatCage (std::shared_ptr<Cat> cat) : m_cat(cat) { m_cat.collar.Scan() } ~CatCage () { //do stuff with cat } }; //DLL-Schnittstelle Cage* Create() { auto cat= std::make_unique<Cat>(); return new CatCage(std::move(cat)); } void Delete(Cage* cage) { delete cage; }
Kann man das so machen oder seht ihr hier Risiken? Die DLL-Schnittstelle ist gegeben.
Bei der ursprünglichen Implementierung wurden raw pointer und new/delete verwendet. In Init-Methoden wurden dann die Member initialisiert. Zugriffe im Konstruktor gab es nicht, im Destruktor wurde auf
NULL
geprüft.
-
An smartptr und move ist erstmal nichts auszusetzen. Aber deine Klassenhierarchie ist überhaupt nicht gut:
Ein CatCage hat einen Zeiger auf eine Katze und auf ein Tier. Das scheint mir nicht so gewollt.
-
Da der Zeiger auf Animal in Cage private ist, sollte doch CatCage nichts damit anfangen können? Im Speicher existiert nur ein Cat-Objekt. Cage und CatCage haben über den Zeiger die entsprechende Sicht darauf.
Edit
Ich habe Cat ein Collar spendiert, um die Problemstellung zu präzisieren. Vermutlich wäre es geschickter Collar nicht als Teil von Cat zu realisieren und separat an CatCage zu übergeben.
-
Welche DLL Schnittstelle? Und welche ursprüngliche Implementierung?
Und was soll das mit Cat und CatCage? Wieso nicht gleich Wurstbrot und Supermarkt? (Sorry, Insiderwitz)
Aber im Ernst: darunter kann man sich doch jetzt nichts vorstellen. Das Programm das du wirklich schreiben willst, wird sicher nicht Katzen und Katzen-Käfige modellieren. Und ohne zu wissen worum es wirklich geht und von was für einer DLL Schnittstelle du sprichst, können wir dir auch nicht sagen ob das so Sinn macht.
Was auffällt ist dass du
shared_ptr
undmake_unique
mischt. Das kann man so machen, kann auch in bestimmten Fällen Sinn machen. Normalerweise nimmt man aber ehermake_shared
wenn manshared_ptr
verwendet.
-
Danke für den Hinweis, tatsächlich ist es das erste mal, dass ich
shared_ptr
einsetze.Ich modifiziere Klassen in einer bestehenden DLL so, dass kein new und delete mehr benötigt wird. Die DLL-Schnittstelle, über die man sich ein Objekt einer gewissen Basisklasse holen kann (oben wäre diese Cage, habe das Bsp. korrigiert), muss bleiben wie sie ist.
Das Beispiel ist wirklich nicht gelungen, aber es hat mir geholfen, das eigentliche Problem zu erkennen (danke @Jockelx ). Ich habe hier eine Art parallele Vererbungshierarchie vorliegen, die nicht zwingend notwendig ist und vermieden werden sollte.
Ansonsten gilt natürlich "Ein Wurstbrot ist ein Supermarkt!"
-
Oops, sorry, ich hatte das Kommentar "//DLL-Schnittstelle" in deinem Code übersehen.
Also grundsätzlich ja, das kannst du so machen. Ich würde wie gesagt das
make_unique
gegenmake_shared
tauschen. Oder den Zeigertyp inCatCage
aufunique_ptr
ändern. Muss aber nicht sein, funktionieren wird es auch so.
-
Die andere Frage, die @Jockelx noch aufgeworfen hatte, war das
private: std::shared_ptr<Animal> m_animal;
in der Basisklasse und das dann folgende
private: std::shared_ptr<Cat> m_cat;
im Katzenkäfig.
Das ist so nicht sinnvoll, denn ein
CatCage
hat jetzt sowohl einm_animal
(wenn auch versteckt) als auch einm_cat
. Wenn du in der Cage-Klasse sowas hast wiestring animal_name() const { return m_animal->name; }
(natürlich unter der Annahme, dass ein Animal einen Namen hat), dann funktioniert das mit demCatCage
nicht mehr, weil dort dasm_animal
ja nicht gesetzt ist. Das ist also eine doofe Idee.Lösungen können sein:
- im
CatCage
einfach einen cast verwenden. Ist dann ein wenig "generic-style" - müssen die alle voneinander erben? Sonst einfach ein
template Cage<T>
machen?
Aber wieso hat das Cage überhaupt genau ein Tier drin? Können nicht vielleicht auch mehrere Tiere eingesperrt sein? Vielleicht sogar unterschiedliche Tiere? Ich finds nicht leicht, hier die richtige Lösung vorzuschlagen. Möglicherweise ist das Klassendesign noch nicht ganz durchdacht.
- im
-
Tierbeispiele finde ich immer schlecht. Es soll jetzt kein Vorwurf an @Kerem sein, es ist leider verbreitet.
Was, wenn einAnimal
irgendeinDevice
ist, eine Katze ist einPrinter
.Cage
könnte irgendeinEnumerator
sein und ein Katzenkäfig könnte somit einPrinterEnum
sein.
Warum sollte nun einPrinterEnum
sowohl einenshared_ptr<Device>
als auch einenshared_ptr<Printer>
haben?
Wäre nicht irgendeine Liste (vector von Smartpointern zum Beispiel, siehe @wob ) angebrachter? Allerdings wäre einPrinterEnum
mit einemstd::vector<Printer>
mit Sicherheit viel angenehmer zu bedienen.
Ich glaube, dass der konkrete Fall sehr viel leichter verständlich für uns alle wäre.
-
Das Beispiel sollte nur helfen die Fragestellung bezüglich der Pointer zu verdeutlichen. Ersetzt man
Animal
durchDevice
undCage
durchDeviceView
entspricht es so ziemlich der eigentlichen Implementierung (@yahendrik warst nah dran :)).Um nicht mehr von Katzen und Käfigen sprechen zu müssen (die bekommt da eh schwer rein^^):
class Device; class DeviceView : public View { private: std::shared_ptr<Device> m_device; public: Cage (std::shared_ptr<Device> device) : m_device(std::move(device)) { } }; class ScriptInterpreter{ void RunScript(const std::string& script) { }; } class DeviceGen1: public Device { private: ScriptInterpreter m_scriptInterpreter; public: ScriptInterpreter& ScriptInterpreter() {return m_scriptInterpreter;} } class DeviceGen1View: public DeviceView { private: std::shared_ptr<DeviceGen1> m_deviceGen1; OnRunButtonClick() { m_deviceGen1->RunScript(script); } public: DeviceGen1View(std::shared_ptr<DeviceGen1> cat) : m_deviceGen1(device) { } }; //DLL-Schnittstelle View* Create() { auto device = std::make_unique<DeviceGen1>(); return new DeviceViewGen1(std::move(device)); } void Delete(View* view) { delete view; }
Es wird wohl darauf hinauslaufen für
ScriptInterpreter
ein eigenes View zu bauen.
-
Das ist hier übrigens immer noch der gleiche Mist wie vorher, d.h.
DeviceGen1View
erbt vonDeviceView
und es gibt somit in demDeviceGen1View
einenstd::shared_ptr<DeviceGen1>
und einen (versteckten)std::shared_ptr<Device>
. Das ist aus den schon genannten Gründen nicht sinnvoll.Warum muss dein DeviceView überhaupt die konkrete (Kind-)Klasse des
Device
kennen?
-
Der Mist wurde ja auch nur umbenannt...
Das konkrete
Device
verfügt über einenScriptInterpreter
, der überDeviceView
bedient werden kann.@Kerem sagte in Smartpointer und move richtig verwendet?:
Es wird wohl darauf hinauslaufen für ScriptInterpreter ein eigenes View zu bauen.
-
@Kerem
Über den Konstruktor vonDeviceGen1View
weisst du doch, dassm_device
aus der Basisklasse nur einDeviceGen1
sein kann. Damit kannst dum_device
doch wieder in einenDeviceGen1
casten. Ist zwar nicht schön, aber was Besseres fällt mir auf die Schnelle auch nicht ein.