Возврат объектов в C ++ - PullRequest
       11

Возврат объектов в C ++

34 голосов
/ 15 октября 2008

Когда вы возвращаете объекты из класса, когда подходящее время освободить память?

Пример,

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

Ответы [ 9 ]

36 голосов
/ 07 января 2009

В зависимости от вашего использования, здесь вы можете выбрать несколько вариантов:

  1. Делайте копию каждый раз, когда создаете животное:

    class AnimalLister 
    {
    public:
      Animal getNewAnimal() 
      {
        return Animal();
      }
    };
    
    int main() {
      AnimalLister al;
      Animal a1 = al.getNewAnimal();
      Animal a2 = al.getNewAnimal();
    }
    

    Плюсы:

    • Легко понять.
    • Не требует дополнительных библиотек или вспомогательного кода.

    Минусы:

    • Требуется Animal, чтобы иметь хорошо себя ведущий конструктор копирования.
    • Может потребоваться много копирования, если Animal является большим и сложным, хотя оптимизация возвращаемого значения может облегчить это во многих ситуациях.
    • Не работает, если вы планируете возвращать подклассы, полученные из Animal, так как они будут нарезаны до простого Animal, теряя все дополнительные данные в подклассе.
  2. Возврат 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, либо другая реализация интеллектуального указателя.
  3. Отслеживание всех 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 s в один delete.
    • Работает с объектными иерархиями (без нарезки объектов).
    • Нет проблем с копированием больших объектов.
    • Нет необходимости в Animal для определения конструктора копирования.
    • Нет необходимости во внешних библиотеках.

    Минусы:

    • Реализация, как написано выше, не является поточно-ориентированной
    • Требуется дополнительный код поддержки
    • Менее ясно, чем две предыдущие схемы
    • Неочевидно, что когда AnimalLister выходит из области видимости, он забирает животных с собой. Вы не можете держаться за Животных дольше, чем за Животного.
24 голосов
/ 15 октября 2008

Я советую возвращать std::tr1::shared_ptr (или boost::shared_ptr, если ваша реализация C ++ не имеет TR1) вместо необработанного указателя. Таким образом, вместо Animal* используйте std::tr1::shared_ptr<Animal>.

Общие указатели обрабатывают отслеживание ссылок для вас и автоматически удаляют объект, если на него не осталось ссылок.

8 голосов
/ 15 октября 2008

Классическая проблема с указателями и выделенной памятью. Речь идет об ответственности - кто отвечает за очистку памяти, выделенной объектом AnimalLister.

Вы можете сохранить указатель на каждого из этих выделенных животных в самом AnimalLister и сделать так, чтобы он все очистил.

Но у вас есть пара указателей на Animals, сидящих там в main (), которые будут ссылаться на удаленную память.

Одна из причин, по которой я считаю, что решения для подсчета ссылок работают лучше, чем развертывание собственного решения.

8 голосов
/ 15 октября 2008

Самый простой способ - вернуть умный указатель вместо обычных указателей. Например:

std::auto_ptr< Animal> getNewAnimal() 
{
  std::auto_ptr< Animal > animal1( new Animal() );
  return animal1;
}

Если вы можете использовать TR1 или Boost, вы также можете использовать shared_ptr <>.

5 голосов
/ 15 октября 2008
  1. shared_ptr (работает хорошо),
  2. верните простой указатель и скажите пользователю вашего класса, что это его животное сейчас, и они несут ответственность за его удаление, когда закончите,
  3. реализует метод freeAnimal (Animal *), который делает очевидным необходимость удаления указателя на животное.

  4. Альтернативный способ - просто вернуть объект животного напрямую, без указателей, без вызовов new. Конструктор копирования гарантирует, что вызывающая сторона получит свой собственный объект животных, который они могут сохранить в куче или стеке, или скопировать в контейнер по своему усмотрению.

Итак:

class AnimalLister 
{
Animal getAnimal() { Animal a; return a; }; // uses fast Return Value Optimisation
};

Animal myownanimal = AnimalLister.getAnimal(); // copy ctors into your Animal object

RVO означает, что возврат объекта вместо указателя на самом деле быстрее (так как компилятор не создает новый объект и не копирует его в объект вызывающего, а непосредственно использует объект вызывающего).

3 голосов
/ 11 июня 2009

В подробном обсуждении Скотта Мейерса он приходит к выводу, что лучше использовать shared_ptr или auto_ptr.

2 голосов
/ 15 октября 2008

Время для освобождения памяти, занимаемой объектом, - это когда вам больше не нужен этот конкретный объект. В вашем конкретном случае пользователь класса AnimalLister запросил указатель на новый выделенный объект класса Animal. Так что именно он отвечает за освобождение памяти, когда ему больше не нужен этот указатель / объект.

AnimalLister lister;
Animal* a = lister.getNewAnimal();
a->sayMeow();
delete a;

На мой взгляд, в этом случае не нужно ничего переусердствовать. AnimalLister - это просто фабрика, которая создает новые объекты Animal и все.

2 голосов
/ 15 октября 2008

Или вы можете использовать подход COM-ish и применить простой подсчет ссылок.

  • Когда вы создаете объект, немедленно присвойте ему эталонное значение 1
  • Когда кто-нибудь получает копию указателя, он добавляет AddRef ()
  • Когда кто-то отдает свою копию указателя, он отпускает ()

Если число ссылок достигает 0, объект удаляется сам.

В конечном счете, это то, что shared_ptr делает под капотом, но дает вам больше контроля над тем, что происходит, и, по моему опыту, легче отлаживать. (Это также очень кроссплатформенный).

Я еще не давал shared_ ptr слишком много шансов в своем развитии, так что это может послужить вашим целям.

0 голосов
/ 08 февраля 2009

Мне очень нравится ответ Джоша, но я подумал, что могу добавить другой шаблон, потому что он еще не был указан. Идея состоит в том, чтобы заставить клиентский код следить за животными.

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;
  }
}
...