Счетные объекты и несколько распределителей - PullRequest
1 голос
/ 22 августа 2010

Это вопрос проектирования, предполагающий C ++ и иерархию объектов с подсчетом ссылок. Многие классы в моей кодовой базе происходят из общего базового класса (ObjectBase), который реализует методы retain () и release () для увеличения и уменьшения счетчика ссылок экземпляра объекта.

Каждый экземпляр объекта может быть создан в стеке или в куче, используя несколько определяемых пользователем распределителей памяти. Чтобы экземпляр объекта совершил самоубийство (удалите это) в методе release (), если retainCount достигает 0, экземпляр должен знать, с каким распределителем он был создан.

В данный момент я выделяю память для экземпляра объекта с использованием произвольного распределителя, затем вызываю размещение new для создания экземпляра объекта и вызываю метод setAllocator () для объекта, чтобы установить распределитель, с которым он был создан. Если объект был создан в стеке, распределитель имеет значение NULL и release () не будет вызывать delete. Этот процесс очень избыточен и потенциально подвержен ошибкам (утечки памяти, если я забуду вызвать setAllocator и т. Д.). В идеале я бы хотел сделать этот процесс одним шагом:

Object* o = myPoolAllocator.allocate<Object>(constructor arguments... );

Но это очень затрудняет поддержку и произвольное количество аргументов конструктора.

Я просто ищу идеи, как решить эту проблему. Мне очень нравится идея иметь возможность ссылаться на объекты count без необходимости полагаться на умный указатель, тем более что большинство классов в любом случае являются производными от общей базы.

Спасибо за вашу помощь.

Флориан

1 Ответ

1 голос
/ 22 августа 2010

Посмотрите на эту статью: Перегрузка нового в C ++ .Вы можете перегрузить оператор new для ObjectBase, чтобы он принимал ваш распределитель в качестве параметра и выполнял остальную часть работы:

void *ObjectBase::operator new(size_t size, Allocator *allocator) {
  void *ptr = allocator->allocate(size);

  // Hack to pre-initialize member before constructor is called
  ObjectBase *obj = static_cast<ObjectBase *>(ptr);
  obj->setAllocator(allocator);

  return ptr;
}

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

А затем аналогичная перегрузка для delete:

void ObjectBase::operator delete(void *ptr) {
  ObjectBase *obj = static_cast<ObjectBase *>(ptr);
  obj->getAllocator()->free(ptr);
}

Затем вы можете создавать объекты, вызывая new (allocator) SomeClass(...), где SomeClass происходит от ObjectBase.

Edit: Одна потенциальная проблема с этим заключается в том, что выбольше не может выделять объекты в стеке, потому что нет способа инициализировать распределитель на NULL без влияния на то, как работает перегруженный new.

Обновление: Есть одинпоследний (грязный) взлом, чтобы заставить его работать как со стеком, так и с динамическим размещением.Вы можете сделать new установить глобальную переменную (статический член класса также будет работать), указывающую на текущий распределитель, и конструктор может использовать это и сбросить его на NULL.Во всех других случаях этот глобальный объект уже будет NULL, поэтому объект, созданный в стеке, получит распределитель NULL.

Allocator *currentAllocator = NULL;

void *ObjectBase::operator new(size_t size, Allocator *allocator) {
  currentAllocator = allocator;
  return allocator->allocate(size);
}

ObjectBase::ObjectBase() {
  setAllocator(currentAllocator);
  currentAllocator = NULL;
}
...