Я собираюсь игнорировать агрегирование. Это не очень четко определенная концепция, и, на мой взгляд, она вызывает больше путаницы, чем того стоит. Композиции и ассоциации вполне достаточно, Крейг Ларман мне так сказал. Возможно, это не тот ответ, который искал ваш инструктор, но вряд ли он будет реализован на C ++ иначе, чем Association.
Не существует единого способа реализации композиции и ассоциации. То, как вы их реализуете, будет зависеть от ваших требований, например, от множественности отношений.
Состав
Самый простой способ реализовать композицию - использовать простую переменную-член Bar
, как вы предложили. Единственное изменение, которое я хотел бы сделать, - это инициализировать bar
в списке инициализаторов членов конструктора:
// COMPOSITION - with simple member variable
class Foo {
private:
Bar bar;
public:
Foo(int baz) : bar(baz) {}
};
Как правило, рекомендуется инициализировать переменные-члены с помощью списка инициализации конструктора, это может быть быстрее, а в некоторых случаях, например, с константными переменными-членами, это единственный способ их инициализировать.
Также может быть причина реализовать композицию с помощью указателя. Например, Bar
может быть полиморфным типом, и вы не знаете конкретный тип во время компиляции. Или, возможно, вы хотите переслать объявление Bar
, чтобы минимизировать зависимости компиляции (см. идиому PIMPL). Или, возможно, кратность этого отношения составляет от 1 до 0..1, и вам необходимо иметь нулевое значение Bar
. Конечно, поскольку это Композиция Foo
должна владеть Bar
, и в современном мире C ++ 11 / C ++ 14 мы предпочитаем использовать интеллектуальные указатели вместо владения необработанными указателями:
// COMPOSITION - with unique_ptr
class Foo {
private:
std::unique_ptr<Bar> bar;
public:
Foo(int baz) : bar(barFactory(baz)) {}
};
Я использовал здесь std::unique_ptr
, потому что Foo
является единственным владельцем Bar
, но вы можете использовать std::shared_ptr
, если другому объекту требуется от std::weak_ptr
до Bar
.
Ассоциация
Ассоциация обычно реализуется с помощью указателя, как вы это сделали:
// Association - with non-owning raw pointer
class Foo {
private:
Bar* bar;
public:
void setBar(Bar* b) { bar = b; }
};
Конечно, вы должны быть уверены, что Bar
будет активен, пока Foo
его использует, иначе у вас будет зависший указатель. Если время жизни Bar
менее ясно, то более подходящим может быть std::weak_ptr
:
// Association - with weak pointer
class Foo {
private:
std::weak_ptr<Bar> bar;
public:
void setBar(std::weak_ptr<Bar> b) { bar = std::move(b); }
void useBar() {
auto b = bar.lock();
if (b)
std::cout << b->baz << "\n";
}
};
Теперь Foo
может использовать Bar
, не опасаясь остаться с висящим указателем:
Foo foo;
{
auto bar = std::make_shared<Bar>(3);
foo.setBar(bar);
foo.useBar(); // ok
}
foo.useBar(); // bar has gone but it is ok
В некоторых случаях, когда право собственности на Bar
действительно неясно, ассоциация может быть реализована с использованием std::shared_ptr
, но я думаю, что это должно быть крайней мерой.
Что касается вашей реализации агрегирования с глубокой копией указателя Bar
. Я бы не сказал, что это типичная реализация, но, как я уже сказал, это зависит от ваших требований. Вам нужно убедиться, что вы вызываете delete
для своего bar
указателя-члена в Foo
деструкторе, хотя в противном случае у вас есть утечка памяти (или используйте интеллектуальный указатель).
person
Chris Drew
schedule
18.09.2014