Ответ 1
Я собираюсь игнорировать агрегацию. Это не очень четко определенная концепция, и, на мой взгляд, это вызывает больше путаницы, чем того стоит. Композиции и ассоциации вполне достаточно, Крейг Ларман сказал мне об этом. Возможно, это не тот ответ, которого искал ваш инструктор, но вряд ли он будет реализован на С++ в любом случае.
Существует не один способ реализации композиции и ассоциации. То, как вы их реализуете, будет зависеть от ваших требований, например от множественности отношений.
Состав
Самый простой способ реализации композиции - использовать переменную типа Bar
, как и вы. Единственное изменение, которое я сделал бы, это инициализировать Bar
в списке инициализации конструктора:
// COMPOSITION - with simple member variable
class Foo {
private:
Bar bar;
public:
Foo(int baz) : bar(baz) {}
};
Как правило, рекомендуется инициализировать переменные-члены с помощью списка инициализации конструктора, это может быть быстрее и в некоторых случаях подобно константным переменным-членам, это единственный способ их инициализировать.
Также может возникнуть причина для реализации композиции с использованием указателя. Например, Bar
может быть полиморфным, и вы не знаете конкретного типа во время компиляции. Или, возможно, вы хотите переслать declare Bar
, чтобы минимизировать зависимости компиляции (см. ИММОМ PIMPL). Или, возможно, множественность этого отношения составляет от 1 до 0..1, и вам нужно иметь нуль Bar
. Конечно, потому что это композиция Foo
должна принадлежать Bar
, а в современном мире С++ 11/С++ 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
to Bar
.
Ассоциация
Ассоциация обычно реализуется с использованием указателя, как вы это делали:
// Association - with non-owning raw pointer
class Foo {
private:
Bar* bar;
public:
void setBar(Bar* b) { bar = b; }
};
Конечно, вам нужно быть уверенным, что Bar
будет жив, пока Foo
использует его, иначе у вас есть обвисший указатель. Если время жизни Bar
менее ясное, тогда a 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
, хотя в противном случае у вас есть утечка памяти (или используйте умный указатель).