Angenommen, ich habe eine Klasse Foobar
, die Klasse verwendet (hängt davon ab) Widget
. In guten Widget
alten Tagen sollte wolud als Feld Foobar
oder als intelligenter Zeiger deklariert werden, wenn polymorphes Verhalten erforderlich ist, und im Konstruktor initialisiert werden:
class Foobar {
Widget widget;
public:
Foobar() : widget(blah blah blah) {}
// or
std::unique_ptr<Widget> widget;
public:
Foobar() : widget(std::make_unique<Widget>(blah blah blah)) {}
(…)
};
Und wir wären fertig. Leider werden uns Java-Kids heute auslachen, wenn sie es sehen, und das zu Recht, wenn es sich paart Foobar
und Widget
zusammen. Die Lösung ist scheinbar einfach: Wenden Sie die Abhängigkeitsinjektion an, um die Konstruktion von Abhängigkeiten aus der Foobar
Klasse herauszunehmen . Aber dann zwingt C ++ uns, über die Inhaberschaft von Abhängigkeiten nachzudenken. Drei Lösungen kommen in den Sinn:
Eindeutiger Zeiger
class Foobar {
std::unique_ptr<Widget> widget;
public:
Foobar(std::unique_ptr<Widget> &&w) : widget(w) {}
(…)
}
Foobar
Ansprüche, die alleiniges Eigentum sind Widget
, gehen auf sie über. Dies hat folgende Vorteile:
- Auswirkungen auf die Leistung sind vernachlässigbar.
- Es ist sicher, da es
Foobar
die Lebensdauer kontrolliertWidget
und so sicherstellt, dassWidget
es nicht plötzlich verschwindet. - Es ist sicher, dass
Widget
es nicht ausläuft und ordnungsgemäß zerstört wird, wenn es nicht mehr benötigt wird.
Dies ist jedoch mit folgenden Kosten verbunden:
- Hiermit wird die Verwendung von
Widget
Instanzen eingeschränkt , z. B. kann kein StapelWidgets
verwendet werden, esWidget
kann kein gemeinsam genutzter Stapel verwendet werden.
Gemeinsamer Zeiger
class Foobar {
std::shared_ptr<Widget> widget;
public:
Foobar(const std::shared_ptr<Widget> &w) : widget(w) {}
(…)
}
Dies ist wahrscheinlich das Äquivalent zu Java und anderen Sprachen mit Speicherbereinigung. Vorteile:
- Universeller, da Abhängigkeiten gemeinsam genutzt werden können.
- Behält die Sicherheit (Punkte 2 und 3) der
unique_ptr
Lösung bei.
Nachteile:
- Verschwendet Ressourcen, wenn keine Freigabe erfolgt.
- Erfordert weiterhin die Zuweisung von Heapspeichern und verbietet die Zuweisung von Stack-Objekten.
Einfacher alter Beobachtungszeiger
class Foobar {
Widget *widget;
public:
Foobar(Widget *w) : widget(w) {}
(…)
}
Platzieren Sie den rohen Zeiger in der Klasse und verlagern Sie die Last des Eigentums auf eine andere Person. Vorteile:
- So einfach wie es nur geht.
- Universal, nimmt gerade irgendwelche an
Widget
.
Nachteile:
- Nicht mehr sicher.
- Führt eine weitere Einheit ein, die für das Eigentum von
Foobar
und verantwortlich istWidget
.
Einige verrückte Template-Metaprogramme
Der einzige Vorteil, den ich mir vorstellen kann, ist, dass ich all die Bücher lesen kann, für die ich keine Zeit gefunden habe, während meine Software erstellt wird;)
Ich neige zur dritten Lösung, da sie am universellsten ist und Foobars
sowieso etwas zu handhaben ist, also ist das Handhaben Widgets
eine einfache Veränderung. Die Verwendung von rohen Zeigern stört mich jedoch, andererseits fühle ich mich als intelligente Zeigerlösung falsch, da sie den Abhängigkeitskonsumenten einschränken, wie diese Abhängigkeit erstellt wird.
Vermisse ich etwas? Oder ist nur Dependency Injection in C ++ nicht trivial? Sollte die Klasse ihre Abhängigkeiten besitzen oder nur beobachten?
Foobar
das der einzige Besitzer ist? Im alten Fall ist es einfach. Das Problem bei DI ist jedoch, wie ich es sehe, dass es die Klasse von der Konstruktion ihrer Abhängigkeiten entkoppelt und sie auch vom Eigentum an diesen Abhängigkeiten entkoppelt (da das Eigentum an die Konstruktion gebunden ist). In Umgebungen mit Speicherbereinigung wie Java ist dies kein Problem. In C ++ ist dies.
std::unique_ptr
ist es der richtige Weg, wenn Foobar den Gültigkeitsbereich verlässt . Mit können Siestd::move()
den Besitz einer Ressource vom oberen Bereich auf die Klasse übertragen.