Должна ли абстрактная фабрика С++ предоставлять метод уничтожения для сконструированных объектов?

Рассмотрим следующий интерфейс (используются тупые указатели, потому что мы все еще в C++98)

class WidgetMaker {
    virtual Widget* makeWidget() = 0;
};

При следующей вероятной реализации

class SpecificWidgetMaker: public WidgetMaker {
    Widget* makeWidget() {
        return new SpecificWidget();
    }
};

Widget — это некий базовый класс с виртуальным деструктором, SpecificWidget расширяет его. Мои коллеги утверждают, что интерфейс WidgetMaker должен содержать следующий метод

virtual void freeWidget(Widget* widget);

Обоснование заключается в том, что таким образом мы не заставляем реализации makeWidget использовать стандартное новое распределение, они могут использовать настраиваемый распределитель пула или всегда возвращать один и тот же глобальный экземпляр в случае, если виджет не имеет состояния или что-то еще.

Я считаю такой дизайн вообще плохой идеей - он усложняет клиентский код, нарушает KISS и YAGNI, затрудняет переход (что маловероятно в нашей организации в ближайшие 20 лет) на unique_ptr. Должен ли я доверять своим чувствам? В каких случаях оправдан свободный метод как часть интерфейса абстрактной фабрики?


person Muxecoid    schedule 10.02.2016    source источник
comment
По крайней мере, вы должны как можно быстрее перейти на умные указатели, это сэкономит много работы и позволит избежать ненужных ошибок.   -  person Erik Alapää    schedule 10.02.2016
comment
Обязательно ли использовать С++98? Если вы это сделаете, это должно быть помечено этим.   -  person NathanOliver    schedule 10.02.2016
comment
TBH Я думаю, что интеллектуальные указатели - правильное решение этой проблемы, поскольку они могут содержать пользовательские средства удаления. Это предложение заставляет вас продолжать использовать необработанные указатели.   -  person Galik    schedule 10.02.2016
comment
Если вы не можете использовать std::shared_ptr из-за C++98, оценили ли вы стоимость реализации собственного аналога? В идеале, что-то, что вы могли бы заменить на typedef или псевдоним типа, как только у вас появится более современная стандартная библиотека C++.   -  person TBBle    schedule 10.02.2016


Ответы (2)


Проблема с предложенным вашим другом решением (на самом деле, также с вашим исходным) заключается в том, что оно имеет излишне нетривиальный протокол. Как только вы получите Widget через makeWidget, вам нужно не забыть освободить его (либо напрямую, либо вызвав какой-либо метод фабрики). Известно, что он хрупкий — он либо быстро выйдет из строя (вызвав Widget утечки), либо действительно усложнит клиентский код.

Если вы посмотрите на интерфейс std::shared_ptr::shared_ptr(...), вы увидите, что он может возьмите собственный объект удаления.

Таким образом, возможно, вы могли бы typedef (или эквивалентно), что именно представляет собой умный указатель Widget:

using WidgetPtr = std::shared_ptr<Widget, ...>

Если позже вы решите, что фабрика должна выполнить какое-то действие, когда Widget освобождается, вы можете изменить typedef на ту, которая использует пользовательский модуль удаления, и этот пользовательский модуль удаления может уведомить фабрику об удалении объекта.

Основное преимущество этого заключается в том, что он снимает с пользователя обязанность помнить об освобождении Widget.

person Ami Tavory    schedule 10.02.2016
comment
Я знаю. Это проблема стандартной версии используемого нами языка программирования. А также люди, не доверяющие бусту. Клиент WidgetMaker, вероятно, получит исключительное право собственности, поэтому общий ptr кажется накладным (в настоящее время он использует некоторый ad-hoc scoped_ptr). Я попробую предложить boost::shared_ptr. - person Muxecoid; 10.02.2016
comment
@Muxecoid Что касается вашего первого пункта, обратите внимание, что теперь он является частью стандарта (больше не исключительно для повышения). - person Ami Tavory; 10.02.2016
comment
@Muxecoid О, вы только что поняли вашу точку зрения по поводу стандартной версии, которую вы используете. Тем не менее, я оставляю предыдущий комментарий для будущей ссылки. - person Ami Tavory; 10.02.2016

Я бы сказал, что здесь нет общего правила. Все зависит от деталей вашей реализации Widgets.

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

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

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

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

Одним из примеров того, почему вы можете захотеть иметь явный freeWidget(), могут быть исключения. Выбрасывать исключения из деструкторов... обидчиво. Это разрешено, но с некоторыми... ограничениями. Таким образом, если существует вероятность того, что уничтожение вашего виджета может вызвать исключение, использование freeWidget() позволит вам лучше контролировать создание исключения в этом случае и правильно очистить уничтоженный виджет перед этим.

person Sam Varshavchik    schedule 10.02.2016