Digamos que eu tenha uma classe Foobar
que use (depende de) classe Widget
. Nos bons dias Widget
antigos , você seria declarado como um campo Foobar
ou, talvez, como um ponteiro inteligente se fosse necessário um comportamento polimórfico, e seria inicializado no construtor:
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 estaríamos prontos e prontos. Infelizmente, hoje em dia, as crianças Java riem de nós quando a veem, e com razão, enquanto se unem Foobar
e se Widget
unem. A solução é aparentemente simples: aplique Injeção de Dependências para tirar a construção de dependências da Foobar
classe. Mas então, o C ++ nos obriga a pensar na propriedade das dependências. Três soluções vêm à mente:
Ponteiro exclusivo
class Foobar {
std::unique_ptr<Widget> widget;
public:
Foobar(std::unique_ptr<Widget> &&w) : widget(w) {}
(…)
}
Foobar
alega que a propriedade exclusiva Widget
é passada a ele. Isso tem as seguintes vantagens:
- O impacto no desempenho é insignificante.
- É seguro, pois
Foobar
controla a vida útilWidget
e garante queWidget
não desapareça repentinamente. - É seguro que
Widget
não vazará e será destruído adequadamente quando não for mais necessário.
No entanto, isso tem um custo:
- Ele coloca restrições sobre como as
Widget
instâncias podem ser usadas, por exemplo, nenhuma pilha alocadaWidgets
pode ser usada, nenhumaWidget
pode ser compartilhada.
Ponteiro compartilhado
class Foobar {
std::shared_ptr<Widget> widget;
public:
Foobar(const std::shared_ptr<Widget> &w) : widget(w) {}
(…)
}
Provavelmente é o equivalente mais próximo de Java e outras linguagens de coleta de lixo. Vantagens:
- Mais universal, pois permite compartilhar dependências.
- Mantém a segurança (pontos 2 e 3) da
unique_ptr
solução.
Desvantagens:
- Desperdiça recursos quando nenhum compartilhamento está envolvido.
- Ainda requer alocação de heap e não permite objetos alocados a pilha.
Ponteiro de observação simples
class Foobar {
Widget *widget;
public:
Foobar(Widget *w) : widget(w) {}
(…)
}
Coloque o ponteiro bruto dentro da classe e transfira o ônus da propriedade para outra pessoa. Prós:
- Tão simples quanto possível.
- Universal, aceita qualquer
Widget
.
Contras:
- Não é mais seguro.
- Introduz outra entidade responsável pela propriedade de ambos
Foobar
eWidget
.
Alguma metaprogramação de modelos loucos
A única vantagem que consigo pensar é que eu seria capaz de ler todos os livros pelos quais não encontrei tempo enquanto meu software está em execução;)
Eu me inclino para a terceira solução, como ela é mais universal, algo precisa ser gerenciado de Foobars
qualquer maneira, portanto, gerenciarWidgets
é uma mudança simples. No entanto, o uso de ponteiros brutos me incomoda, por outro lado, a solução de ponteiro inteligente parece errada para mim, pois faz o consumidor de dependência restringir como essa dependência é criada.
Estou esquecendo de algo? Ou apenas Injeção de Dependência em C ++ não é trivial? A classe deve possuir suas dependências ou apenas observá-las?
Foobar
é o único proprietário? No caso antigo, é simples. Mas o problema com o DI, a meu ver, é que ele separa a classe da construção de suas dependências, mas também a propriedade dessas dependências (como a propriedade está ligada à construção). Em ambientes de coleta de lixo, como Java, isso não é um problema. Em C ++, é isso.
std::unique_ptr
é o caminho a percorrer. Você pode usarstd::move()
para transferir a propriedade de um recurso do escopo superior para a classe.