Как работать с разными стратегиями владения указателем на член? - PullRequest
1 голос
/ 28 июля 2010

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

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

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

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

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

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

Ответы [ 4 ]

2 голосов
/ 29 июля 2010

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

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

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

2 голосов
/ 28 июля 2010

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

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

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

0 голосов
/ 29 июля 2010

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

0 голосов
/ 28 июля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...