Как работать с разными стратегиями владения указателем на член?

Рассмотрим следующую структуру классов:

class Filter
{
    virtual void filter() = 0;
    virtual ~Filter() { }
};

class FilterChain : public Filter
{
    FilterChain(collection<Filter*> filters)
    {
         // copies "filters" to some internal list
         // (the pointers are copied, not the filters themselves)
    }

    ~FilterChain()
    {
         // What do I do here?
    }

    void filter()
    {
         // execute filters in sequence
    }
};

Я выставляю класс в библиотеке, поэтому у меня нет контроля над тем, как он будет использоваться.

В настоящее время у меня есть некоторые проблемы с дизайном, касающиеся владения Filter объекты FilterChain держит указатели на. Более конкретно, вот два возможных сценария использования FilterChain:

  • Сценарий A: некоторые функции в моей библиотеке создают (возможно, сложную) цепочку фильтров, распределяют память по мере необходимости и возвращают вновь распределенные FilterChain объект. Например, одна из этих функций создает цепочку фильтров из файла, которая может описывать произвольно сложные фильтры (включая цепочки фильтров цепочек фильтров и т. Д.). Пользователь функции несет ответственность за уничтожение объекта после завершения работы.
  • Сценарий Б: пользователь имеет доступ к группе Filter объекты, и хочет объединить их в цепочки фильтров определенным образом. Пользователь конструирует FilterChain объекты для собственного использования, а затем уничтожить их, когда он закончил с ними. Filter объекты не должны быть уничтожены, когда FilterChain ссылка на них уничтожена.

Теперь два простейших способа управления собственностью в FilterChain Объектом являются:

  • FilterChain владеть Filter объекты. Это означает, что объекты, на которые ссылаются FilterChain уничтожены в FilterChainдеструктор. Что несовместимо со сценарием Б.
  • FilterChain не владеет Filter объекты. Это означает FilterChainДеструктор ничего не делает. Теперь существует проблема со сценарием А, потому что пользователь должен знать внутреннюю структуру всех Filter объекты, задействованные для того, чтобы уничтожить их все, не пропустив ни одного, как родитель FilterChain не делает это сам. Это просто плохой дизайн, и просить утечки памяти.

Следовательно, мне нужно что-то более сложное. Мое первое предположение состоит в том, чтобы создать умный указатель с настраиваемым логическим флагом, указывающим, является ли умный указатель владельцем объекта. Тогда вместо того, чтобы брать коллекцию указателей на Filter объекты, FilterChain взял бы коллекцию умных указателей на Filter объекты. когда FilterChainДеструктор называется, он уничтожит умные указатели. Деструктор самого умного указателя затем уничтожил бы объект, на который указывает Filter объект) тогда и только тогда, когда установлен логический флаг, указывающий на владельца.

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

4 ответа

Решение

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

С помощью shared_ptr удалит большинство этих проблем одним махом и сделает ваш дизайн намного проще. Единственная потенциальная ошибка, которую я думаю здесь, - это если ваш фильтр содержит циклы. Я вижу, что это может произойти, если у вас есть какая-то петля обратной связи. В этом случае я бы предложил иметь все Filter объекты, принадлежащие одному классу, а затем FilterChain будет хранить слабые указатели на Filter объекты.

Держу пари, что время выполнения этапов фильтрации будет намного превышать дополнительные издержки на разыменование умного указателя. shared_ptr разработан, чтобы быть довольно легким.

Являются ли фильтры настолько большими, что вы не можете просто сделать глубокую копию каждого из них при создании FilterChain? Если вы смогли сделать это, то все ваши проблемы исчезнут: FilterChain всегда убирает за собой.

Если это не вариант из-за проблем с памятью, тогда используйте shared_ptr кажется, имеет смысл. Звонящий должен нести ответственность за сохранение shared_ptr для каждого объекта это заботится, а затем FilterChain будет знать, следует ли удалять определенные фильтры или нет, когда это deleteд.

РЕДАКТИРОВАТЬ: Как отметил Нил Filter нужен виртуальный деструктор.

Я бы пошел с FilterChain, не владеющим объектами Filter. Затем в вашей библиотеке, когда вам нужно будет загрузить FilterChain из файла, у вас будет другой объект Loader, который отвечает за время жизни объектов Filter. Таким образом, FilterChain будет работать согласованно для цепочек, загруженных библиотекой, и цепочек, созданных пользователем.

FilterChain должен иметь отдельный DeleteAll() метод, который перебирает коллекцию и deleteс фильтрами. Он вызывается в сценарии A и не вызывается в сценарии B. Это действительно требует некоторого интеллекта со стороны пользователей FilterChain, но не более того, чтобы помнить delete и возражать, что они new"D. (Они должны быть в состоянии справиться с этим, или они заслуживают утечки памяти)

Другие вопросы по тегам