C ++ идиома, чтобы избежать утечек памяти? - PullRequest
0 голосов
/ 06 июня 2009

В следующем коде возникает утечка памяти, если Info::addPart1() вызван несколько раз случайно:

typedef struct
{
}part1;

typedef struct
{
}part2;

class Info
{
    private:
    part1* _ptr1;
    part2* _ptr2;    

    public:
    Info()
    {
      _ptr1 = _ptr2 = NULL;
    }

    ~Info()
    {
      delete _ptr1; 
      delete _ptr2;
    }

    addPart1()
    {
       _ptr1 = new part1;         
    }

    addPart2()
    {
      _ptr2 = new part2;         
    }   
};


Info _wrapper;
_wrapper.addPart1();
_wrapper.addPart2();

Существует ли идиома C ++ для решения этой проблемы?

Я мог бы переписать addPart1 и addPart2 вот так, чтобы защитить MLK

addPart1()
{
  if(_ptr1 != NULL) delete _ptr1;
  _ptr1 = new part1;         
}

Это хорошее решение?

Ответы [ 11 ]

10 голосов
/ 06 июня 2009

Используйте умный указатель, такой как boost: shared_ptr, boost: scoped_ptr рекомендуется для управления необработанным указателем. auto_ptr сложно работать, на это нужно обратить внимание.

5 голосов
/ 06 июня 2009

Потерпи меня здесь ...

В далеком прошлом программисты использовали такие конструкции, как «jump» и «goto» для управления потоком. В конце концов появились общие закономерности и появились такие конструкции, как for, do / while, вызов функций и try / catch, и спагетти были приручены. Эти именованные конструкции дают намного больше информации о намерении, чем общее goto, где вы должны изучить остальную часть кода для контекста, чтобы понять, что он делает. В маловероятном случае, когда вы увидите переход в современном коде от компетентного кодера, вы знаете, что происходит что-то довольно необычное.

По моему мнению, "удалить" - это "переход" к управлению памятью. Современному разработчику доступно достаточно классов интеллектуальных указателей и контейнеров, поэтому у большинства кодов нет особых причин содержать одно явное удаление (конечно, кроме реализаций интеллектуальных указателей). Когда вы видите просто «удалить», вы не получаете информации о намерении; когда вы видите scoped_ptr / auto_ptr / shared_ptr / ptr_container, вы получаете гораздо больше.

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

Обновление 2013-01-27: Отмечу, что превосходный доклад Херба Саттера о C ++ 11 включает в себя некоторые аналогичные чувства по поводу удаления свободного кода.

5 голосов
/ 06 июня 2009

Вы должны прочитать об идиоме умного указателя и о RAII . Я предлагаю взглянуть на новый технический отчет (TR1).
Взгляните здесь и здесь .
Также взгляните на умные указатели Boost.
Я рекомендую классы loki-lib SmartPtr или StrongPtr.

4 голосов
/ 06 июня 2009

Проверка ненулевого указателя перед удалением является избыточной. delete 0 гарантированно не будет.

Обычный способ справиться с этим -

delete _ptr1;
_ptr1 = 0;
_ptr1 = new part1;

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

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

Предлагаемое исправление будет работать (хотя, конечно, вы все еще рискуете утечку памяти, если addPart2 () вызывается дважды). Гораздо более безопасный подход - использовать scoped_ptr из коллекции библиотеки Boost (www.boost.org), которая является контейнером, который действует как указатель, но гарантирует, что его цель удаляется при уничтожении контейнера. Ваш пересмотренный класс будет выглядеть как

class Info
{
    private:
    boost::scoped_ptr<part1> _ptr1;
    boost::scoped_ptr<part2> _ptr2;    

    public:
    Info() {}  // scoped_ptrs default to null

    // You no longer need an explicit destructor- the implicit destructor
    // works because the scoped_ptr destructor handles deletion

    addPart1()
    {
      _ptr1.reset(new part1);
    }

    addPart2()
    {
      _ptr2.reset(new part2);         
    }   
};

Как правило, рекомендуется избегать написания кода, требующего явного удаления указателей. Вместо этого попробуйте использовать контейнеры, которые делают это автоматически в соответствующее время. Boost - хороший ресурс для подобных вещей.

Все это предполагает, что у вас есть причина, по которой ptr1_ и ptr2_ должны быть указателями. Если нет, то гораздо лучше сделать из них обычные предметы; тогда вы получаете управление памятью бесплатно.

2 голосов
/ 06 июня 2009

Использовать конструкцию вместо инициализации.

class Info
{
    private:
    part1* _ptr1;
    part2* _ptr2;    

    public:
    Info() : _ptr1(new part1), _ptr2(new part2)
    {
    }

    ~Info()
    {
      delete _ptr1; 
      delete _ptr2;
    }
};

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

class Info
{
    private:
    part1 _part1;
    part2 _part2;    

    public:
    Info()
    {
    }

    ~Info()
    {
    }
};

Но я думаю, что вы хотите, чтобы указатели создавались лениво, тогда я бы не советовал создавать общедоступные методы класса, которые заботятся об инициализации. Это должно быть обработано внутри класса, когда класс должен выделить их.

1 голос
/ 06 июня 2009

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

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

Info(const Info &rhs)
{
  _ptr1 = new part1[rhs._ptr1];
  _ptr2 = new part2[rhs._ptr2];
}

У вас будет похожая проблема с оператором присваивания по умолчанию.

Если вы выберете правильный умный указатель, эти проблемы исчезнут. :)

1 голос
/ 06 июня 2009

Если вы хотите, чтобы у него было ленивое поведение, вы могли бы рассмотреть это:

addPart1()
{
    if(_ptr1 == NULL) {
        _ptr1 = new part1;
    }
}

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

0 голосов
/ 06 июня 2009

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

0 голосов
/ 06 июня 2009

Вы должны взглянуть на RAII

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...