Уничтожение данных в C ++ - PullRequest
       13

Уничтожение данных в C ++

0 голосов
/ 09 ноября 2009

Итак, для класса я (постоянно изобретаю колесо) пишу набор стандартных структур данных, таких как связанные списки и карты. У меня все работает нормально, вроде. Вставка и удаление данных работает как брелок.

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

Все данные вставляются в список следующими способами:

/*
Adds the specified data to the back of the list.
*/
template<typename T, class COMPFUNCTOR>
void List<T, COMPFUNCTOR>::append(T* d)
{
    if(tail != NULL)
    {//If not an empty list, simply alter the tail.
        tail->setNext(new ListNode<T>(d));
        tail = tail->getNext();
    }
    else
    {//If an empty list, alter both tail and head.
        head = tail = new ListNode<T>(d);
    }
    size++;
};

/*
Adds a copy of the specified data to the back of the list.
*/
template<typename T, class COMPFUNCTOR>
void List<T, COMPFUNCTOR>::append(const T& d)
{
    this->append(new T(d));
};

Первый метод предполагает, что он владеет данными, переданными в него; второй копирует данные, переданные в него. Теперь для основного:

int main(int argc, char** argv)
{
    parser = new Arguments(argc, argv); //Uses a map<char, list<string>>; no direct  bugs, insertion works fine.
    if(parser->flagSet('f'))
    {
        printf("%s\n", parser->getArg('f').getFirst().str.c_str());
    }
    return 0;
}

Это приводит к дампу стека для события с двойным освобождением. Деструктор списка определяется следующим образом:

/*
Destroys the List and all data inside it.
*/
template<typename T, class COMPFUNCTOR>
List<T, COMPFUNCTOR>::~List()
{
    while(head != NULL)
    {
        ListNode<T>* tmp = head; //Set up for iteration.
        head = head->getNext();
        if(tmp->getData() != NULL) //Delete this node's data then the node itself.
            delete tmp->getData();
        delete tmp;
    }
};

Если я закомментирую либо деструктор списка, либо код в операторе if оператора main, программа будет работать нормально. Теперь я не уверен, откуда приходит это двойное удаление.

Список уничтожается в конце main, что приводит к удалению данных внутри него; который либо принадлежит, либо скопирован в него, и из него когда-либо выходят только копии (единственный раз, когда список пропускает указатели своих данных, это когда вы удаляете его из списка)

Очевидно, что что-то создается в стеке в main, когда . Parser-> getArg ( 'е') GetFirst (); называется.

Я читаю это как, (указатель де-реф на парсер) -> (получить ссылку на связанный список). (получить копию первого элемента в списке [an std :: string]);

Удаление указателя на синтаксический анализатор не представляет особой проблемы (на самом деле, мне, вероятно, следует это удалить, упс); удаление ссылки тоже не должно быть большим делом (просто прикованный указатель); и удаление копии первого элемента не должно быть проблемой. Где я ошибся? EDIT Код для ListNode выглядит следующим образом:

    /*
    Create a ListNode with the specified neighbor.
    */
    template<typename T>
    ListNode<T>::ListNode(T* d, ListNode<T>::ListNode* neighbor)
    {
        data = d;
        next = neighbor;
    }

    /*
    Deletes the ListNode.
    */
    template<typename T>
    ListNode<T>::~ListNode()
    {
        next = NULL;
        if(data != NULL)
            delete data;
        data = NULL; 
    }

ListNodes только когда-либо получают указатели на свои данные, они только удаляют свои данные, когда они умирают с ненулевыми указателями данных. Сам список также всегда удаляет материал, если он не равен нулю. Все удаленные данные имеют значение NULL.

Да, и сейчас данные - это std :: string, я не могу контролировать их конструктор копирования, но я бы предположил, что он правильно реализован.

Ответы [ 6 ]

3 голосов
/ 09 ноября 2009

Деструктор ListNode также удаляет свои данные?

Редактировать: ах, теперь, когда вы опубликовали свой источник - обратите внимание, что при удалении не устанавливается ноль ... так что ваш нулевой тест все равно даст ложь.

1 голос
/ 12 ноября 2009

Глядя на ваш класс ListNode, очевидно, что существует несоответствие прав собственности. Хорошим практическим правилом для проектирования является то, что не только каждое выделение должно соответствовать совпадающему выделению, но и что оно должно выполняться на одном уровне кода или, в идеале, одним и тем же объектом. То же самое относится к любому ресурсу, приобретение которого должно быть сопряжено с выпуском.

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

    template<typename T>
    ListNode<T>::ListNode(T* d, ListNode<T>::ListNode* neighbor)
    {
        data = d;
        next = neighbor;
    }

    template<typename T>
    ListNode<T>::~ListNode()
    {
        next = NULL;
        if(data != NULL)
            delete data;
        data = NULL; 
    }

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

Если вы сделаете это, а затем убедитесь, что ваш класс ListNode всегда выделяет и освобождает копию элемента, вы можете сделать элемент данных T вместо T*. Это означает, что вы можете сделать свой класс примерно таким:

template<typename T>
class ListNode
{
public:

    explicit ListNode( const T& d )
        : data(d), next()
    {
    }

    T& getData()              { return data; }
    const T& getData() const  { return data; }
    ListNode* getNext() const { return next; }
    void setNext(ListNode* p) { next = p; }

private:
    ListNode* next;
    T data;
}

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

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

template<typename T, class COMPFUNCTOR>
void List<T, COMPFUNCTOR>::append(T* d);

template<typename T, class COMPFUNCTOR>
void List<T, COMPFUNCTOR>::append(const T& d);

С изменением ListNode выше мы можем реализовать второй из них, не нуждаясь в помощи первого.

template<typename T, class COMPFUNCTOR?
void List<T, COMPFUNCTOR>::append(const T& d)
{
    ListNode<T>* newNode = new ListNode<T>(d);

    if (!tail)
        tail->setNext( newNode );
    else
        head = newNode;

    tail = newNode;
    size++;
}

Деструктор 'walk' для List остается тем же, что и у вас, за исключением того, что не следует пытаться вручную удалить собственные данные ListNode, что происходит автоматически при удалении самого ListNode.

Примечание: весь мой код не проверен, только для экспозиции!

1 голос
/ 10 ноября 2009

Вы соблюдали правило трех ? Двойное удаление часто происходит, когда копируется объект, который должен «управлять пуанти». Либо сделайте ваши узлы не копируемыми и не присваиваемыми, либо определите свой собственный ctor и оператор присваивания "правильно".

1 голос
/ 10 ноября 2009

Это не источник вашей проблемы, но вам не нужно проверять, является ли что-то NULL или нет, прежде чем удалить его. Вызов delete на NULL - не работает. Я не думаю, что вы сделали эту ошибку, но я видел, как люди пишут

if(data != NULL)
  delete data;

и думаю, что поведение

if(data is properly allocated)
  delete data;

что, конечно, не соответствует действительности.

1 голос
/ 09 ноября 2009

Вы не опубликовали код для него, но я предполагаю, что деструктор ListNode удаляет его данные после того, как вы уже удалили его в деструкторе List.

0 голосов
/ 09 ноября 2009

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

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

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