Supponiamo che io abbia una classe Foobarche usa (dipende da) classe Widget. Ai Widgetvecchi tempi, il lupo veniva dichiarato come un campo Foobaro forse come un puntatore intelligente se fosse necessario un comportamento polimorfico e sarebbe stato inizializzato nel costruttore:
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)) {}
(…)
};
E saremmo pronti e pronti. Sfortunatamente, oggi, i bambini Java rideranno di noi quando lo vedranno, e giustamente, mentre si accoppiano Foobare Widgetinsieme. La soluzione è apparentemente semplice: applicare Dependency Injection per eliminare la costruzione di dipendenze Foobar. Ma poi, C ++ ci costringe a pensare alla proprietà delle dipendenze. Mi vengono in mente tre soluzioni:
Puntatore unico
class Foobar {
std::unique_ptr<Widget> widget;
public:
Foobar(std::unique_ptr<Widget> &&w) : widget(w) {}
(…)
}
Foobarrivendica la proprietà esclusiva di Widgetciò che gli viene passato. Ciò presenta i seguenti vantaggi:
- L'impatto sulle prestazioni è trascurabile.
- È sicuro, poiché
Foobarcontrolla la durata della vitaWidget, quindi si assicura cheWidgetnon scompaia improvvisamente. - È sicuro che
Widgetnon perderà e verrà distrutto correttamente quando non sarà più necessario.
Tuttavia, questo ha un costo:
- Pone delle restrizioni su come utilizzare le
Widgetistanze, ad es. Non èWidgetspossibile utilizzare alcuna allocazione di stack , non èWidgetpossibile condividere.
Puntatore condiviso
class Foobar {
std::shared_ptr<Widget> widget;
public:
Foobar(const std::shared_ptr<Widget> &w) : widget(w) {}
(…)
}
Questo è probabilmente l'equivalente più vicino ad altre lingue raccolte di immondizia e Java. vantaggi:
- Più universale, in quanto consente la condivisione delle dipendenze.
- Mantiene la sicurezza (punti 2 e 3) della
unique_ptrsoluzione.
svantaggi:
- Spreca risorse quando non è coinvolta alcuna condivisione.
- Richiede ancora l'allocazione dell'heap e non consente gli oggetti allocati nello stack.
Semplice puntatore osservatore
class Foobar {
Widget *widget;
public:
Foobar(Widget *w) : widget(w) {}
(…)
}
Posiziona il puntatore non elaborato all'interno della classe e sposta l'onere della proprietà su qualcun altro. Professionisti:
- Il più semplice possibile.
- Universale, accetta qualsiasi
Widget.
Contro:
- Non più al sicuro.
- Introduce un'altra entità che è responsabile della proprietà di entrambi
FoobareWidget.
Qualche metaprogrammazione di modelli pazzi
L'unico vantaggio che mi viene in mente è che sarei in grado di leggere tutti quei libri per i quali non ho trovato il tempo mentre il mio software sta funzionando;)
Mi rivolgo alla terza soluzione, poiché è la più universale, qualcosa deve essere gestito Foobarscomunque, quindi gestire Widgetsè un semplice cambiamento. Tuttavia, l'utilizzo di puntatori non elaborati mi disturba, d'altra parte la soluzione di puntatore intelligente mi sembra sbagliata, poiché fanno sì che il consumatore di dipendenza limiti il modo in cui tale dipendenza viene creata.
Mi sto perdendo qualcosa? O semplicemente l'iniezione di dipendenza in C ++ non è banale? La classe dovrebbe possedere le sue dipendenze o semplicemente osservarle?
Foobarè l'unico proprietario? Nel vecchio caso è semplice. Ma il problema con DI, come la vedo io, è che separa la classe dalla costruzione delle sue dipendenze, la separa anche dalla proprietà di quelle dipendenze (poiché la proprietà è legata alla costruzione). In ambienti garbage collection come Java, questo non è un problema. In C ++, questo è.
std::unique_ptrè la strada da percorrere. È possibile utilizzarestd::move()per trasferire la proprietà di una risorsa dall'ambito superiore alla classe.