агрегация классов проектирования - распределение стека против динамического выделения памяти - PullRequest
11 голосов
/ 23 ноября 2010

Пожалуйста, посмотрите на два упрощенных примера проектирования агрегации классов ниже.

Решение 1

Заголовок

// need include, forward declaration is not enough
#include "door.h"

class CGarage
{
public:
    CGarage(const std::string &val);

private:
    CDoor m_door;
};

Источник

#include "garage.h"
CGarage::CGarage(const std::string &val)
        :m_door(val)
{
}

Решение 2

Заголовок

#include "smart_ptr.hpp"

// forward declaration
class CDoor;

class CGarage
{
public:
    CGarage(const std::string &val);

private:
    scoped_ptr<CDoor> m_door;
};

Источник

#include "garage.h"
#include "door.h"

CGarage::CGarage(const std::string &val)
        :m_door(new CDoor(val))
{
}

Вопросы относительно создания члена CDoor

Какие преимущества / недостатки вы видите в дизайне примеров (динамическое размещение CDoor по сравнению с автоматическим распределением)?

Эточто я придумал:

Решение 1:
+ нет проблем с обработкой памяти или временем жизни
+ нет необходимости в дорогостоящем выделении памяти во время выполнения
- нужно дополнительное включение в заголовок (скорость компиляциимедленнее ?, более тесная связь с CDoor) -> многие включения в заголовочные файлы считаются плохими ...

Решение 2:
+ слабая связь с CDoor в заголовке (требуется только прямое объявление)
-память должна обрабатываться программистом

Какой десgn вы обычно предпочитаете по какой причине?

Ответы [ 7 ]

8 голосов
/ 23 ноября 2010

Редко, когда мы получаем дизайн вопросов (я имею в виду, интересные).

Давайте на минутку забудем (очевидно) надуманный пример и сконцентрируемся на понятии.

Мы имеем2 решения:

  • Hard сдерживание: потяните заголовок и создайте объект напрямую
  • Soft сдерживание: вперед объявите заголовок ииспользуйте указатель

Я добровольно откажусь от всех аргументов "производительности" на данный момент.Производительность не имеет значения в 97% случаев (говорит Кнут), поэтому, если мы не измерим заметную разницу, поскольку функциональность идентична, нам не нужно беспокоиться об этом в данный момент.

Поэтому у нас есть две ортогональные концепции, пытающиеся повлиять на наше решение:

  • Зависимость заставляет нас склоняться к Мягкому сдерживанию
  • Простота заставляет нас склоняться к Жесткому сдерживанию

Некоторые ответы здесь справедливо говорят о полиморфизме, но точная реализация Door - это деталь, которая DoorБеспокойство, а не Garage.Если Door желает предложить несколько реализаций, это нормально, если его клиенты не должны беспокоиться об этой детали.

Я сам фанат принципов KISS и YAGNI.Поэтому я бы поспорил в пользу жесткого сдерживания ... с одним предупреждением .

При разработке интерфейса, который будет открыт, поэтому интерфейс, который стоит наГраница библиотеки, то этот интерфейс должен предоставлять минимум зависимостей и внутренних компонентов.В идеале это должен быть Facade или Proxy, объект, единственной целью которого является скрытие внутренних элементов библиотеки, и этот объект должен иметь минимальные зависимости в своем заголовке и иметь максимальную совместимость с макетом, что означает:

  • нет виртуального метода
  • простой указатель в качестве атрибута (Pimpl)

Для всех внутренних классов простота не дает покоя.

2 голосов
/ 23 ноября 2010

Решение 1 превосходит как во время выполнения, так и во время компиляции во всех мыслимых случаях, если только у вас нет экстремальных проблем с зависимостями include и вы должны действовать для их уменьшения.Решение 2 имеет больше проблем, чем вы упомянули - вам нужно написать и поддерживать дополнительный конструктор копирования / оператор присваивания, просто для начала.

1 голос
/ 23 ноября 2010

Для меня эти проекты эквивалентны. В каждом случае CDoor принадлежит CGarage.

Я предпочитаю 1., поскольку shared_ptr во втором не добавляет ничего, кроме сложности - с кем CGarage делится? Твои минусы для 1. не убедительны для меня.

Почему бы не использовать scoped_ptr в 2., если вы не предоставляете геттер для объекта CDoor?

1 голос
/ 23 ноября 2010

Это не только вопрос связывания (на самом деле это далеко не так: динамическое размещение становится действительно интересным, если вы используете полиморфизм).Классическое эмпирическое правило: если у вас есть объект в вашем классе, сделайте это.Это то же самое, что и в функции: если вы можете иметь локальную переменную, возьмите ее, не занимайте память для кошмарной отладки.

Например, если вам понадобится объединить неизвестное количество компонентов, указатели (общие, умные или немые) - ваши друзья.Вот, например, если вы не знаете, сколько дверей будет в вашем гараже, хорошая идея - указатели (фактически, не общие) и динамическое размещение.

Если у вас есть объект, используемыйдругой объект, который всегда будет одного и того же класса, и который бесполезен после того, как его владелец мертв, с какой стати вам нужно проходить динамическое распределение?Но ради себя постарайтесь иметь как можно меньше динамических объектов.

0 голосов
/ 23 ноября 2010

Еще несколько моментов для рассмотрения:

Решение 1 (при условии, что CDoor не является typedef для типа указателя):

  • Не «дружественный к полиморфизму», так как вы будете копировать объекты по значению при инициализации (даже если вы передадите по ссылке). Пожалуйста, смотрите проблему "нарезки классов": Что такое нарезка объектов?
  • Вы не можете реализовать идиому pimpl для быстрого копирования / инициализации CGarage

В общем случае (1) означает, что CGarage тесно связан с CDoor. Конечно, вы можете добиться большей гибкости, если CDoor - это своего рода адаптер / декоратор

Решение 2:

  • Классы связаны менее плотно
  • Дорогое выделение кучи
  • Дополнительные расходы на умный указатель

Ни один из проектов не может быть предпочтительным, «обычно», это полностью зависит от того, за что моделирует ваш класс, за что он отвечает и как он будет использоваться.

Если кто-то может посоветовать вам дальше, пожалуйста, изучите «Шаблоны проектирования C ++», чтобы получить более глубокое понимание.

Это должно быть хорошо для начала:

0 голосов
/ 23 ноября 2010

Если два гаража не разделяют одну и ту же дверь, решение № 1 в виде shared_ptr создает впечатление, что дверь является общей.

0 голосов
/ 23 ноября 2010

Решение 1:

Вы выставляете заголовок Door, который является только деталью реализации вашего класса. Если Door был частью общедоступного интерфейса Garage, вы можете предположить, что пользователи Garage тоже будут использовать Door, но там, где он закрыт, гораздо лучше не показываться.

Решение 2:

Использование shared_ptr означает, что если вы копируете гараж, ваша копия имеет ту же дверь. Не такой же, как тот же. Если на одном из ваших гаражей вы покрасите дверь в зеленый цвет, у обоих ваших гаражей будут зеленые двери. Вы должны понять эту проблему.

Где ваш класс находится в вашем коде, играет важную роль в том, какой из них лучше использовать. Если Garage является частью вашего общедоступного интерфейса, а Door вообще нигде в общедоступном интерфейсе, то очень выгодно отделить его, возможно, с помощью shared_ptr.

Если Garage нигде не является частью вашего общедоступного интерфейса, но является деталью реализации, я бы не заботился о проблеме связывания.

Если Гараж и Дверь находятся в вашем общедоступном интерфейсе. и Door очень часто используется с Garage, и Door.h не вводит еще больше заголовков, но довольно легок, вы можете избежать агрегации как объекта (включая заголовок).

...