Digamos que tengo una clase Foobar
que usa (depende de) la clase Widget
. En los viejos tiempos, Widget
wolud se declararía como un campo Foobar
, o tal vez como un puntero inteligente si fuera necesario un comportamiento polimórfico, y se inicializaría en el constructor:
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)) {}
(…)
};
Y estaríamos listos y listos. Desafortunadamente, hoy, los niños de Java se reirán de nosotros una vez que lo vean, y con razón, como parejas Foobar
y Widget
juntos. La solución es aparentemente simple: aplique la inyección de dependencias para eliminar la construcción de dependencias fuera de Foobar
clase. Pero entonces, C ++ nos obliga a pensar en la propiedad de las dependencias. Se me ocurren tres soluciones:
Puntero único
class Foobar {
std::unique_ptr<Widget> widget;
public:
Foobar(std::unique_ptr<Widget> &&w) : widget(w) {}
(…)
}
Foobar
reclama la propiedad exclusiva de lo Widget
que se le transfiere. Esto tiene las siguientes ventajas:
- El impacto en el rendimiento es insignificante.
- Es seguro, ya que
Foobar
controla su vida útilWidget
, por lo que se asegura de queWidget
no desaparezca repentinamente. - Es seguro que
Widget
no tendrá fugas y se destruirá correctamente cuando ya no sea necesario.
Sin embargo, esto tiene un costo:
- Establece restricciones sobre cómo
Widget
se pueden usar las instancias, por ejemplo, no se puede usar una pila asignadaWidgets
, noWidget
se puede compartir.
Puntero compartido
class Foobar {
std::shared_ptr<Widget> widget;
public:
Foobar(const std::shared_ptr<Widget> &w) : widget(w) {}
(…)
}
Este es probablemente el equivalente más cercano de Java y otros lenguajes recolectados de basura. Ventajas:
- Más universal, ya que permite compartir dependencias.
- Mantiene la seguridad (puntos 2 y 3) de la
unique_ptr
solución.
Desventajas
- Malgasta recursos cuando no se trata de compartir.
- Todavía requiere la asignación del montón y no permite los objetos asignados a la pila.
Puntero de observación simple
class Foobar {
Widget *widget;
public:
Foobar(Widget *w) : widget(w) {}
(…)
}
Coloque el puntero en bruto dentro de la clase y transfiera la carga de la propiedad a otra persona. Pros:
- Tan simple como puede ser.
- Universal, acepta cualquiera
Widget
.
Contras:
- Ya no es seguro.
- Introduce otra entidad responsable de la propiedad de ambos
Foobar
yWidget
.
Alguna metaprogramación de plantillas locas
La única ventaja que se me ocurre es que sería capaz de leer todos esos libros para los que no encontré tiempo mientras mi software se está acumulando;)
Me inclino hacia la tercera solución, ya que es más universal, de Foobars
todos modos hay que administrar algo , por lo que administrar Widgets
es un cambio simple. Sin embargo, el uso de punteros sin formato me molesta, por otro lado, la solución de puntero inteligente me parece incorrecta, ya que hacen que el consumidor de dependencia restrinja cómo se crea esa dependencia.
¿Me estoy perdiendo de algo? ¿O simplemente la inyección de dependencia en C ++ no es trivial? ¿Debería la clase poseer sus dependencias o simplemente observarlas?
Foobar
es el único propietario? En el viejo caso es simple. Pero el problema con DI, como lo veo, es que al desacoplar la clase de la construcción de sus dependencias, también la desacopla de la propiedad de esas dependencias (ya que la propiedad está vinculada a la construcción). En entornos de recolección de basura como Java, eso no es un problema. En C ++, esto es.
std::unique_ptr
es el camino a seguir. Puede usarstd::move()
para transferir la propiedad de un recurso desde el ámbito superior a la clase.