Подтвердить что ты не робот

Агрегация класса проектирования - распределение стека и распределение динамической памяти

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

Решение 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 в заголовке (требуется только предварительная декларация)
- память должна обрабатываться программистом

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

4b9b3361

Ответ 1

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

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

Имеем 2 решения:

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

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

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

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

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

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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

Вкратце: контекст - это все, но, ради вас самих, старайтесь как можно меньше динамического объекта.

Ответ 4

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

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

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

Ответ 5

Решение 1:

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

Решение 2:

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

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

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

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

Ответ 6

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

Ответ 7

Еще несколько замечаний:

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

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

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

Решение 2:

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

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

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

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