Angenommen, ich habe eine Klasse Foobar, die Klasse verwendet (hängt davon ab) Widget. In guten Widgetalten Tagen sollte wolud als Feld Foobaroder 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 Foobarund Widgetzusammen. Die Lösung ist scheinbar einfach: Wenden Sie die Abhängigkeitsinjektion an, um die Konstruktion von Abhängigkeiten aus der FoobarKlasse 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) {}
(…)
}
FoobarAnsprü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
Foobardie Lebensdauer kontrolliertWidgetund so sicherstellt, dassWidgetes nicht plötzlich verschwindet. - Es ist sicher, dass
Widgetes 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
WidgetInstanzen eingeschränkt , z. B. kann kein StapelWidgetsverwendet werden, esWidgetkann 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_ptrLö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
Foobarund 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 Foobarssowieso etwas zu handhaben ist, also ist das Handhaben Widgetseine 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?
Foobardas 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_ptrist 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.