Как работать с разными стратегиями владения указателем на член?
Рассмотрим следующую структуру классов:
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. (Они должны быть в состоянии справиться с этим, или они заслуживают утечки памяти)