Скажем, у меня есть класс, Foobar
который использует (зависит от) класса Widget
. В Widget
старые добрые времена wolud объявлялся как поле в Foobar
или, может быть, как умный указатель, если требовалось полиморфное поведение, и он был бы инициализирован в конструкторе:
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)) {}
(…)
};
И мы были бы все готово и сделано. К сожалению, сегодня дети Java будут смеяться над нами, как только они это увидят, и по праву, как они соединяются Foobar
и Widget
вместе. Решение на первый взгляд простое: примените Dependency Injection, чтобы вывести построение зависимостей из Foobar
класса. Но затем C ++ заставляет нас задуматься о владении зависимостями. На ум приходят три решения:
Уникальный указатель
class Foobar {
std::unique_ptr<Widget> widget;
public:
Foobar(std::unique_ptr<Widget> &&w) : widget(w) {}
(…)
}
Foobar
утверждает, что единоличная собственность на Widget
это передана ему. Это имеет следующие преимущества:
- Влияние на производительность незначительно.
- Это безопасно, так как
Foobar
контролирует время жизниWidget
, поэтому оноWidget
не пропадает внезапно. - Это безопасно, что
Widget
не будет течь и будет должным образом разрушено, когда больше не нужно.
Тем не менее, это происходит за счет:
- Он накладывает ограничения на то, как
Widget
могут использоваться экземпляры, например, нельзя использовать ни один выделенный стекWidgets
, ниWidget
общий.
Общий указатель
class Foobar {
std::shared_ptr<Widget> widget;
public:
Foobar(const std::shared_ptr<Widget> &w) : widget(w) {}
(…)
}
Это, вероятно, самый близкий эквивалент Java и других языков для сборки мусора. Преимущества:
- Более универсальный, так как позволяет обмениваться зависимостями.
- Поддерживает безопасность (пункты 2 и 3)
unique_ptr
решения.
Недостатки:
- Тратит ресурсы, когда не происходит обмена.
- По-прежнему требует выделения кучи и запрещает объекты, выделенные стеком.
Обычный наблюдатель
class Foobar {
Widget *widget;
public:
Foobar(Widget *w) : widget(w) {}
(…)
}
Поместите необработанный указатель в класс и перенесите бремя владения на другого. Плюсы:
- Так просто, как только можно.
- Универсальный, принимает только любой
Widget
.
Минусы:
- Больше не безопасно.
- Представляет другое юридическое лицо, которое отвечает за владение обоими
Foobar
иWidget
.
Какой-то сумасшедший шаблон метапрограммирования
Единственное преимущество, которое я могу придумать, - это то, что я смогу читать все те книги, на которые у меня не было времени, пока я строил свое программное обеспечение;)
Я склоняюсь к третьему решению, так как оно является наиболее универсальным, в Foobars
любом случае чем-то нужно управлять , поэтому управление Widgets
- это простое изменение. Тем не менее, использование необработанных указателей беспокоит меня, с другой стороны, решение для интеллектуальных указателей кажется мне неправильным, поскольку они заставляют потребителя зависимости ограничивать создание этой зависимости.
Я что-то пропустил? Или просто внедрение зависимостей в C ++ не тривиально? Должен ли класс владеть своими зависимостями или просто наблюдать за ними?
Foobar
это единственный владелец? В старом случае все просто. Но проблема с DI, на мой взгляд, заключается в том, что он отделяет класс от построения его зависимостей, он также отделяет его от владения этими зависимостями (так как право собственности связано со строительством). В средах со сборкой мусора, таких как Java, это не проблема. В C ++ это так.
std::unique_ptr
это путь. Вы можете использоватьstd::move()
для переноса владения ресурсом из верхней области в класс.