Подтвердить что ты не робот

Возвращаемые объекты в С++

При возврате объектов из класса, когда нужно время для освобождения памяти?

Пример

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 не имеет способа отслеживать список созданных животных, поэтому как изменить логику такого кода, чтобы иметь способ удалить созданные объекты.

4b9b3361

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

Ответ 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;
  }
}