Вызывает ли это утечку памяти / Как мне структурировать код - PullRequest
1 голос
/ 17 августа 2011

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

Описание: У меня есть базовый класс Pet с производными классами Cat, Dog и Bird.Я разбираю строки из файла, и в зависимости от определенного содержимого в этой строке мне нужно создать экземпляр производных классов, а затем снова проанализировать часть строки определенным образом.Вот пример файла:

Dog Spot Brown,Labrador,5
Cat Felix Black,7
Bird Polly Green,Parrot,12,Crackers

И некоторый код:

class Pet
{
protected:
  string _type;
  string _name;
  string _desc;

public:
  Pet();
  bool ParseLine(std::string line);
  string Type() { return _type; }
  string Name() { return _name; }
  string Desc() { return _desc; }
};

class Dog : public Pet
{
private:
  string _color;
  string _type;
  int _age;

public:
  Dog(string type, string name, string desc);
  bool ParseDesc(string desc);
};

Основной код:

ifstream infile(filename, ifstream::in);
string line;
while(getline(infile, line))
{
   Pet* pet = new Pet();    // "new" called once
   if(pet->ParseLine(line))
   {
      if(pet->Type() == "Dog")
      {
         pet = new Dog(pet->Type(), pet->Name(), pet->Desc());   // "new" called again
         pet->ParseDesc(pet->Desc());
      }
      else if(pet->Type() == "Cat")
      {
         // ...
      }
   }
}

В основном происходит следующее:

Я беру строку из файла и разбираю ее на три поля (вот что делает ParseLine():

Type (Dog, Cat, Bird, etc.)
Name (Spot, Felix, Polly, etc.)
Description ("Brown,Labrador,5", "Black,7", "Green,Parrot,12,Crackers", etc)

Затем я присваиваю эти три поля моей переменной Pet*.Затем, в соответствии со значением в Pet*->Type(), я анализирую Pet*->Desc(), чтобы получить дополнительную информацию для этого конкретного типа животного.

Я беспокоюсь о вызове оператора "new" дважды.Я думаю, что, возможно, есть лучший способ отформатировать код, который мог бы вообще избежать этого.

Я действительно хотел бы сохранить мою getline() рутину.Я НЕ хочу заглядывать в строку, чтобы определить тип, а затем решить, как создать свой экземпляр.

Кроме того, я должен переназначить свои переменные _type, _name, and _desc, когда я воссоздаю Dog(), иЯ бы предпочел не делать этого.

Спасибо.

-

В частности, как мне этого избежать:

Pet* pet = new Pet();
pet->ParseLine(line);
string type = pet->Type();
string name = pet->Name();
string desc = pet->Desc();
delete pet;
if(type == "Dog")
{
   Pet* dog = new Dog(type, name, desc);
   dog->ParseDesc(desc);
}

Ответы [ 7 ]

8 голосов
/ 17 августа 2011

Да, это вызывает утечку памяти, поскольку вы выделяете new Pet(), который никогда не удаляется, а указатель на него переопределяется либо new Dog(), либо чем-то другим.

Я бы предложил вам создатьтак называемая фабричная функция, которая читает строку из файла и создает тип Pet состояния строки.

3 голосов
/ 17 августа 2011

Да, есть утечка. Если вы не хотите выпускать указатели вручную, используйте некоторую библиотеку интеллектуальных указателей, например, Boost shared_ptr .

Что касается утечек в вашем коде: когда вы снова вызываете new и присваиваете тот же указатель, у вас есть утечка. Когда вы покидаете область видимости, содержащую указатель, и не освобождаете память, возникает другая утечка.

Тем не менее, весь ваш дизайн пахнет для меня и выглядит неправильно. Вы не должны создавать питомца только для того, чтобы потом понять, что это собака или кошка, и воссоздать его. Вместо этого у вас должен быть какой-то «создатель» (фабричный) объект, который бы справился с этим, и ParseLine будет членом этого фабричного объекта, а не собаки или питомца.

2 голосов
/ 17 августа 2011

В дополнение к другим комментариям здесь вам нужен виртуальный деструктор в базовом классе, чтобы гарантировать, что производные классы очищаются должным образом при удалении через Pet*.

virtual ~Pet() {}

См. здесь для обоснования.

1 голос
/ 18 августа 2011

Я согласен с комментарием о boost :: shared_ptr, узнайте, как его использовать - это сделает вашу жизнь лучшеФункция ParseLine не должна быть методом экземпляра Pet (домашнее животное не разбирает строку, домашнее животное лает, ест, гуляет и т. Д.)

У вас должен быть класс PetFactory, который имеет статический метод ParseLine и который возвращаетполиморфный питомец * (или лучше PetPtr)

1 голос
/ 17 августа 2011

Вот что я сделал в похожей ситуации.Обратите внимание, что это не единственный или лучший способ сделать это.

Я читал записи произвольной длины из файла, в котором каждая запись имела общий заголовок фиксированного размера, а тип записи / объекта определялся изИнформация заголовка.Я создал фабричный класс, который читает заголовок, ищет в контейнере соответствующую запись и использует фабричный класс / функцию для создания нужного объекта.Данные заголовка были переданы в конструктор нового объекта для копирования.

В простом псевдокоде это было что-то вроде:

struct header_t
{
       int Type
       int Size
       ....
}

map<Type, CreateFunction> RecordFactory = 
{
      { TYPE1, CreateRecord1 },
      { TYPEX, CreateRecordX },
      ....
}

header_t NewHeader = ReadHeader()
RecordCreate = RecordFactory.Find(NewHeader.Type)
if (RecordCreate is valid) NewRecord = RecordCreate(NewHeader)

Если у вас небольшое фиксированное количество классов, тогда выне обязательно нужен сложный фабричный класс и короткий список if / switch будет работать так же хорошо.Если вы не знакомы с Factory Pattern , прочтите его, так как он полезен в различных ситуациях.

0 голосов
/ 17 августа 2011

Для обнаружения утечек памяти: -

  1. Доступны такие инструменты, как очистить и valgrind

  2. Когда у вас нет доступа к этимИнструменты, есть простой метод Поместите фрагмент кода, который вы сомневаетесь в утечке памяти в бесконечный цикл.Пусть программа запускается в этом цикле (может быть, в течение ночи или около того). Если происходит утечка памяти, память будет завершена, и новые распределения прекратятся.Если вы обнаружите, что он работает нормально, наслаждайтесь отсутствием утечки памяти.

Надеюсь, это поможет.

0 голосов
/ 17 августа 2011

Эмпирическое правило всегда следует удалять после вызова new.
если (домашнее животное) удалить питомца;

...