Возвращаемые объекты в С++
При возврате объектов из класса, когда нужно время для освобождения памяти?
Пример
class AnimalLister
{
public:
Animal* getNewAnimal()
{
Animal* animal1 = new Animal();
return animal1;
}
}
Если я создаю экземпляр Animal Lister и получаю ссылку Animal от него, тогда где я должен его удалить?
int main() {
AnimalLister al;
Animal *a1, *a2;
a1 = al.getNewAnimal();
a2 = al.getNewAnimal();
}
Проблема здесь в AnimalLister не имеет способа отслеживать список созданных животных, поэтому как изменить логику такого кода, чтобы иметь способ удалить созданные объекты.
Ответы
Ответ 1
В зависимости от вашего использования есть несколько вариантов, которые вы можете использовать здесь:
-
Сделайте копию каждый раз, когда вы создаете животное:
class AnimalLister
{
public:
Animal getNewAnimal()
{
return Animal();
}
};
int main() {
AnimalLister al;
Animal a1 = al.getNewAnimal();
Animal a2 = al.getNewAnimal();
}
Плюсы:
- Легко понять.
- Не требует дополнительных библиотек или поддерживающего кода.
Минусы:
- Для этого требуется
Animal
иметь хорошо выполненный экземпляр-конструктор.
- Это может привести к большому количеству копий, если
Animal
является большим и сложным, хотя оптимизация возвращаемого значения может облегчить это во многих ситуациях.
- Не работает, если вы планируете возвращать подклассы, полученные из
Animal
, поскольку они будут нарезаны до простой Animal
, потеряв все дополнительные данные в подклассе.
-
Верните a shared_ptr<Animal>
:
class AnimalLister
{
public:
shared_ptr<Animal> getNewAnimal()
{
return new Animal();
}
};
int main() {
AnimalLister al;
shared_ptr<Animal> a1 = al.getNewAnimal();
shared_ptr<Animal> a2 = al.getNewAnimal();
}
Плюсы:
- Работает с объектно-иерархическими (без наложения объектов).
- Нет проблем с копированием больших объектов.
- Нет необходимости в
Animal
для определения конструктора копии.
Минусы:
- Требуется библиотека Boost или TR1 или другая реализация интеллектуального указателя.
-
Отслеживать все Animal
распределения в AnimalLister
class AnimalLister
{
vector<Animal *> Animals;
public:
Animal *getNewAnimal()
{
Animals.push_back(NULL);
Animals.back() = new Animal();
return Animals.back();
}
~AnimalLister()
{
for(vector<Animal *>::iterator iAnimal = Animals.begin(); iAnimal != Animals.end(); ++iAnimal)
delete *iAnimal;
}
};
int main() {
AnimalLister al;
Animal *a1 = al.getNewAnimal();
Animal *a2 = al.getNewAnimal();
} // All the animals get deleted when al goes out of scope.
Плюсы:
- Идеально подходит для ситуаций, когда вам нужна куча
Animal
в течение ограниченного времени и планируйте выпустить их все сразу.
- Легко адаптируется к настраиваемым пулам памяти и освобождает все
Animal
в одном delete
.
- Работает с объектно-иерархическими (без наложения объектов).
- Нет проблем с копированием больших объектов.
- Нет необходимости в
Animal
для определения конструктора копии.
- Нет необходимости в внешних библиотеках.
Минусы:
- Реализация, как написано выше, не является потокобезопасной
- Требуется дополнительный код поддержки
- Меньше ясности, чем предыдущие две схемы.
- Не очевидно, что, когда AnimalLister выходит из сферы действия, он собирается взять с собой Животные. Вы не можете держаться за животных дольше, чем вы вешаете на AnimalLister.
Ответ 2
Я советую вернуть std::tr1::shared_ptr
(или boost::shared_ptr
, если ваша реализация на С++ не имеет TR1) вместо необработанного указателя. Итак, вместо Animal*
используйте std::tr1::shared_ptr<Animal>
.
Общие указатели обрабатывают отслеживание ссылок для вас и автоматически удаляют объект, если ссылки на него отсутствуют.
Ответ 3
Самый простой способ - вернуть умный указатель вместо обычных указателей.
Например:
std::auto_ptr< Animal> getNewAnimal()
{
std::auto_ptr< Animal > animal1( new Animal() );
return animal1;
}
Если вы можете использовать TR1 или Boost, вы также можете использовать shared_ptr < > .
Ответ 4
Вид классической проблемы с указателями и выделенной памятью. Это об ответственности - кто отвечает за очистку памяти, выделенной объектом AnimalLister.
Вы можете сохранить указатель на каждый из выделенных животных в самом AnimalLister и очистить его.
Но у вас есть пара указателей на Животные, сидящие там в main(), которые будут ссылаться на память, которая была удалена.
Одна из причин, по которой, по моему мнению, решения для подсчета ссылок работают лучше, чем развертывание собственного решения.
Ответ 5
- shared_ptr (который работает хорошо),
- верните простой указатель и сообщите пользователю своего класса, что это их животное сейчас, и они несут ответственность за его удаление, когда закончили,
-
реализовать метод freeAnimal (Animal *) ', который делает очевидным, что требуется удаление указателя на животное.
-
Альтернативный способ - просто вернуть объект животного напрямую, без указателей, без вызовов на новый. Конструктор копирования гарантирует, что вызывающий объект получает свой собственный объект животного, который они могут хранить в куче или стеке, или копировать в контейнер по своему усмотрению.
Итак:
class AnimalLister
{
Animal getAnimal() { Animal a; return a; }; // uses fast Return Value Optimisation
};
Animal myownanimal = AnimalLister.getAnimal(); // copy ctors into your Animal object
RVO означает, что возвращение объекта вместо указателя происходит быстрее (поскольку компилятор не создает новый объект и не копирует его в объект-вызывающий объект, но напрямую использует объект-вызывающий объект).
Ответ 6
В подробное обсуждение Scott Meyers, он заключает, что использование shared_ptr или auto_ptr является лучшим.
Ответ 7
Или вы можете следовать подходу COM-иша и применять простой подсчет ссылок.
- Когда вы создаете объект, немедленно дайте ему ссылочное значение 1
- Когда кто-то получает копию указателя, они AddRef()
- Когда кто-то отказывается от своей копии указателя, они Release()
Если счетчик ссылок достигает 0, объект удаляет себя.
В конечном счете, что делает shared_ptr под капотом, но он дает вам больше контроля над тем, что происходит, и в моем опыте легче отлаживать. (Его также очень кросс-платформенный).
Я еще не дал shared_ ptr слишком большого шанса в моем развитии, так что это может отлично служить вашим целям.
Ответ 8
Время освобождения памяти, занимаемой объектом, - это когда вам больше не нужен этот конкретный объект. В вашем конкретном случае пользователь класса AnimalLister запросил указатель на новый выделенный объект класса Animal. Итак, он тот, кто отвечает за освобождение памяти, когда ему действительно нужен этот указатель/объект.
AnimalLister lister;
Animal* a = lister.getNewAnimal();
a->sayMeow();
delete a;
По моему мнению, в этом случае нет необходимости переучивать что-либо. AnimalLister - это всего лишь factory, который создает новые объекты Animal и что он.
Ответ 9
Мне очень нравится ответ Джоша, но я подумал, что могу добавить другой шаблон, потому что он еще не указан. Идея заключается в том, чтобы заставить клиентский код справляться с отслеживанием животных.
class Animal
{
...
private:
//only let the lister create or delete animals.
Animal() { ... }
~Animal() { ... }
friend class AnimalLister;
...
}
class AnimalLister
{
static s_count = 0;
public:
~AnimalLister() { ASSERT(s_count == 0); } //warn if all animals didn't get cleaned up
Animal* NewAnimal()
{
++count;
return new Animal();
}
void FreeAnimal(Animal* a)
{
delete a;
--s_count;
}
}