Supponiamo che io abbia una classe Foobar
che usa (dipende da) classe Widget
. Ai Widget
vecchi tempi, il lupo veniva dichiarato come un campo Foobar
o 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 Foobar
e Widget
insieme. 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) {}
(…)
}
Foobar
rivendica la proprietà esclusiva di Widget
ciò che gli viene passato. Ciò presenta i seguenti vantaggi:
- L'impatto sulle prestazioni è trascurabile.
- È sicuro, poiché
Foobar
controlla la durata della vitaWidget
, quindi si assicura cheWidget
non scompaia improvvisamente. - È sicuro che
Widget
non perderà e verrà distrutto correttamente quando non sarà più necessario.
Tuttavia, questo ha un costo:
- Pone delle restrizioni su come utilizzare le
Widget
istanze, ad es. Non èWidgets
possibile utilizzare alcuna allocazione di stack , non èWidget
possibile 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_ptr
soluzione.
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
Foobar
eWidget
.
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 Foobars
comunque, 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.