Dependency injection



  • @it0101 sagte in Dependency injection:

    Ich behelfe mir dann meist mit nem smartpointer

    Warum?
    Smartpointer sagt doch dass genaue Gegenteil von Referenz ("gehört mir komplett/ein bisschen" versus "gehört mir überhaupt nicht").



  • @jockelx sagte in Dependency injection:

    "gehört mir überhaupt nicht"

    ist wohl eher ein "gehört mir zwar überhaupt nicht, aber sieh gefälligst zu, daß es gültig bleibt!".



  • Ja, und das heisst?
    Immer und überall shared_ptr, weil man zu sehen muss, dass es gültig bleibt?
    Nee, danke.



  • Was spricht gegen eine Kopie (außer höherer Speicherverbrauch)?



  • Allgemein sicherlich überhaupt nichts.
    Allerdings wissen wir ja nicht, was der TE mit seinem

    const A& a_;
    

    bezwecken will. Und da jetzt shared_ptr als Alternative vorzuschlagen, fand ich sehr komisch/unpassend.



  • @swordfish sagte in Dependency injection:

    Was spricht gegen eine Kopie (außer höherer Speicherverbrauch)?

    Bei einer Kopie hast du das Problem, dass du die Änderungen im "Hauptobjekt" nicht mitbekommst.
    Wenn das Objekt konstant ist, nehm ich auch die Kopie, aber wenn du eine Const-Referenz einsetzt, dann gehts dir ja in aller Regel darum, dass du Zugriff auf Informationen dieses Objekts hast, auch wenn es sich ändert. Da hilft dir eine Kopie natürlich nicht.

    Der Smartpointer ( in meinem Fall meist std::shared_ptr<T> ) hat halt den bereits erwähnten Vorteil, dass er gültig bleibt, genau wie die Referenz. Sonst könnte man auch einen normalen Pointer nehmen, aber da greift man immer ins Ungewisse weil sich die Gültigkeit nicht prüfen lässt.

    @Jockelx :
    was wäre denn deine bevorzugte Lösung, wenn man ein Objekt referenzieren will, was sich ändert, d.h. wo die Kopie nicht infrage kommt?
    Vielleicht gefällt mir die ja besser als die shared_ptr-Variante. Ich habe das Problem zwar selten, aber so richtig zufrieden bin ich da mit meiner shared_ptr-Variante nicht.



  • @it0101 sagte in Dependency injection:

    Der Smartpointer ( in meinem Fall meist std::shared_ptr<T> ) hat halt den bereits erwähnten Vorteil, dass er gültig bleibt, genau wie die Referenz. Sonst könnte man auch einen normalen Pointer nehmen, aber da greift man immer ins Ungewisse weil sich die Gültigkeit nicht prüfen lässt.

    Ich nehme da den normalen Pointer.
    Die 'Gültigkeit' mit smartpointern ist ja ein Trugschluss, nur weil es nicht 'knallt', wenn du ein Objekt nutzt, was es eigentlich nicht mehr geben dürfte.
    Dadurch wird es ja nicht richtig, vielleicht sogar nur schwerer zu erkennen.



  • @jockelx sagte in Dependency injection:

    Die 'Gültigkeit' mit smartpointern ist ja ein Trugschluss, nur weil es nicht 'knallt', wenn du ein Objekt nutzt, was es eigentlich nicht mehr geben dürfte.

    Warum ist das ein Trugschluss? Wenn man mit den Smartpointern so umgeht wie das vorgesehen ist
    ( also nicht Sachen wie "delete myptr.get();" macht ), dann sollte das eigentlich passen. Im Allgemeinen habe ich den Eindruck, dass die Anwendung von Smartpointern Software sogar weniger fehleranfällig gemacht hat und auch die Anfälligkeit für Memoryleaks reduziert.



  • @jockelx sagte in Dependency injection:

    Die 'Gültigkeit' mit smartpointern ist ja ein Trugschluss, nur weil es nicht 'knallt', wenn du ein Objekt nutzt, was es eigentlich nicht mehr geben dürfte.

    Wann kann man denn durch einen Smart Pointer ein Objekt nutzen, das es eigentlich nicht mehr geben dürfte? Und warum dürfte es dieses Objekt wann nicht mehr geben?



  • @it0101
    Semantisch ist dein Programm dann doch immer noch falsch, das meinte ich mit Trugschluss.
    Blöd sowas allgemein/abstrakt zu diskutieren; ich hab z.B. in meinem Kopf den Fall, dass die Klasse nur SINNVOLL arbeiten kann, wenn das Referenz/Pointer Objekt auch wirklich länger lebt, als die Klasse selber.
    Wenn du was anderes im Kopf hast, dann ist ein smart_ptr viellciht auch gut.

    @swordfish
    Siehe oben. Von der Programmlogik dürfte es das Objekt nicht mehr geben. Sonst wäre der normale Pointer auch noch gültig.



  • @jockelx sagte in Dependency injection:

    @it0101
    dass die Klasse nur SINNVOLL arbeiten kann, wenn das Referenz/Pointer Objekt auch wirklich länger lebt, als die Klasse selber.

    Und genau das stellst du ja mit dem shared_ptr sicher. Das referenzierte Objekt ( im shared_ptr ) lebt mindestens solange wie die Instanz deiner Klasse.

    Genau deswegen sind bei mir Smartpointer im Allgemeinen der Notnagel für fast alles, wo Kopien und Referenzen an ihre Grenzen stoßen 😎
    "Normale Pointer" verwende ich eigentlich nur im Bereich Buffer lesen/schreiben.
    Würdest du in meinem aktuellen Projekt nach den Keywords "new" oder "delete" suchen, würdest du herb enttäuscht werden :face_with_stuck-out_tongue:



  • @it0101
    Ja, das ist doch klar, aber das ist halt gerne auch mal Mist.

    class Window
    {..}
    
    class Button(Window* parent)...
    
    

    Mein Button darf nicht länger leben, als mein Window.
    Wenn mein Window zerstört wird, dann soll bitte kein Child leben und denken, es könnte noch irgendwas sinnvolles mit dem Window anfangen.
    Das ist falsch programmiert und das will ich dann nicht durch einen smart_ptr vertuschen, indem das Programm nicht knallt, sondern irgendwas unsinniges mit dem nicht mehr existierendem Fenster macht.



  • @jockelx sagte in Dependency injection:

    @it0101
    Ja, das ist doch klar, aber das ist halt gerne auch mal Mist.

    class Window
    {..}
    
    class Button(Window* parent)...
    
    

    Mein Button darf nicht länger leben, als mein Window.
    Wenn mein Window zerstört wird, dann soll bitte kein Child leben und denken, es könnte noch irgendwas sinnvolles mit dem Window anfangen.
    Das ist falsch programmiert und das will ich dann nicht durch einen smart_ptr vertuschen, indem das Programm nicht knallt, sondern irgendwas unsinniges mit dem nicht mehr existierendem Fenster macht.

    In deinem konstruierten Fall hat du recht.
    Aber in dem Fall, der hier besprochen wird, wo man eine const-Referenz im Konstruktor übergibt, handelt es sich meist um Objekte, die
    a) dem referenzierenden Objekt übergeordnet sind
    oder
    b) gar keine Beziehung zum referenzierenden Objekt haben ( Properties-Klasse z.B. )
    aber eher nicht um eine untergeordnete Beziehung ( Child ).
    Ein Child ( "Button" ) würde man der Klasse vermutlich nicht als Referenz übergeben, sondern dort in der Klasse ("Window") erzeugen und somit die Lebenszeit des Childs ( "Button" ) auf die Lebenzeit der Klasse ("Window") begrenzen. Auch das geht wiederum mit smartpointern, wenn man das will.



  • @it0101

    Aha. '*parent' im Konstruktor ist ein total konstruiertes Beispiel und const-Referenz im Konstruktor sind meist dieses und jenes...;-)

    Ist glaube ich für uns beide nicht mehr sonderlich gewinnbringend, die Diskussion.
    Wichtig ist ja nur, dass man weiß, wann man was zu nehmen hat und ich denke, das wissen wir beide.



  • @jockelx sagte in Dependency injection:

    @it0101

    Aha. '*parent' im Konstruktor ist ein total konstruiertes Beispiel und const-Referenz im Konstruktor sind meist dieses und jenes...;-)

    Ist glaube ich für uns beide nicht mehr sonderlich gewinnbringend, die Diskussion.
    Wichtig ist ja nur, dass man weiß, wann man was zu nehmen hat und ich denke, das wissen wir beide.

    Ich habe ja auch nie gesagt dass die Lösung mit dem klassischen Pointer falsch ist. Das würde ich mir auch nie anmaßen, da mir die theoretische Informatikerausbildung fehlt. Ich komme eher aus der technischen Ecke und daher ist mir eine Lösung so lieb wie die andere 😎



  • @sewing sagte in Dependency injection:

    ist das halten einer Referenz in dieser Form denn als schlechter Stil im Allgemeinen zu vermeiden?

    Kommt auf den Kontext an. Bei Proxy-Klassen ist das durchaus üblich (siehe z.B. Expression Templates oder std::vector<bool>::reference). Diese will man oft auch gerne leichtgewichtig halten, so dass ein std::shared_ptr Overkill wäre.

    std::string_view ist auch noch ein Beispiel wo man sowas macht ohne dass ich es als "schlechten Stil" bezeichnen würde.
    Solche Referenzen sollte man natürlich immer mit Bedacht einsetzen, da man sich damit schnell böse Schellen einfahren kann wenn dem Anwender nicht bewusst ist, dass er selbst dafür verantwortlich ist, dass das referenzierte Objekt länger leben muss als die Instanz, welche die Referenz hält. Leider kann man das auch nur über die Dokumentation kommunizieren - will man es idiotensicher machen, muss man eben mit std::shared_ptr oder einer Kopie arbeiten - oder die Klasse nur in einem Programmkontext verwenden wo die Lebensdauer implizit zugesichert ist (z.B. ein Member, der eine Referenz auf die umschließende Klasseninstanz hält).

    Solche Referenzen oder die Alternative nicht-besitzender Zeiger sind natürlich eine sehr schlanke Lösung, bei er die die Referenz haltenden Instanzen nicht selten komplett wegoptimiert werden. Sie sollten aber keine Standardlösung sein.


Anmelden zum Antworten