Вопрос новичка о ручном управлении памятью и глубоком копировании - PullRequest
1 голос
/ 22 января 2009

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

У меня есть класс, узел для использования в двусвязном списке. Таким образом, в основном это имеет значение и два указателя на другие узлы. Основной конструктор выглядит как Node(const std::string & val, Node * prev, Node * next). Упражнение включает в себя конструктор копирования, который делает поверхностную копию другого узла, с комментарием над ним, в котором говорится, что нужно изменить его на создание глубокой копии.

Вот что я думал, что означало:

Node(const Node & other)
      : value(other.value)
{
  prev = new Node(other.prev->value, other.prev->prev, other.prev->next);
  next = new Node(other.next->value, other.next->prev, other.next->next);
}

Это похоже на достижение цели сделать так, чтобы изменение скопированного узла не влияло на новый узел. Однако, когда я делаю это таким образом, я размещаю новые вещи в куче. Это беспокоит меня, потому что я думаю, что это означает, что я должен также удалить его в деструкторе узла. Но это теперь несовместимо с другим конструктором, где указатели на узлы просто передаются, уже указывая на что-то. Я не могу по праву идти delete в next и prev в деструктор с этим, верно?

Я действительно запутался, руководство ценится!

РЕДАКТИРОВАТЬ: Вот код (до моего изменения выше), как было запрошено:

#include <string>

//! Node implements a doubly-linked list node
class Node {
    friend class LinkedList;  //!< LinkedList can access private members of Node
public:

    //!  Constructor
    Node(const std::string & v, Node * p, Node * n) :
      value(v), prev(p), next(n)
    {
    }

    //! Change to deep copy
    Node(const Node & other) :
      value(other.value), prev(other.prev), next(other.next)
    {
    }

    //!  Read-only public methods for use by clients of the LinkedList class
    const std::string & GetValue() const
    {
      return value;
    }


    Node * GetPrevious()const
    {
      return prev;
    }


    Node * GetNext()const
    {
      return next;
    }

    //! Change to deep copy
    Node & operator=(const Node & other)
    {
        if(this!=&other)
        {
            value=other.value;
            prev=other.prev;
            next=other.next;
        }
        return *this;
    }

 private:
    std::string value;        //!< value stored in the node
    Node * prev;            //!< pointer to previous node in the list
    Node * next;            //!< pointer to next node in the list
};

Ответы [ 5 ]

2 голосов
/ 22 января 2009

Вы правы в беспокойстве.

При передаче указателей в конструктор Node информация о владении, передаваемая указателем, отсутствует. Это плохой дизайн конструктора. Либо вы должны передать ссылку, указывающую, что вы не являетесь владельцем следующего узла, либо передать std :: auto_ptr <>, который указывает, что вы должны стать владельцем. Можно утверждать, что next или prev могут быть NULL (начало или конец списка), и поэтому вы не можете использовать ссылки, но это можно преодолеть, если использовать альтернативные конструкторы.

Конечно, есть исключения:
Является ли класс Node частным членом другого класса. В этом случае использование класса Node полностью контролируется владельцем, и, следовательно, его правильное использование будет контролироваться классом-владельцем.

То, что вы не предоставили, это определение деструктора. Благодаря этому мы сможем определить, действительно ли узел получает владение указателем, который передается конструкторам (или если next и prev уже являются умными указателями)?

2 голосов
/ 22 января 2009

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

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

List* List::CopyList()
{
    List* nlist = new List();
    ListNode* node = NULL, prev = NULL;
    ListNode* newNodes = new ListNode[m_nodeCount];
    int i = 0;
    while ((node == NULL && node = m_first) || (node = node->Next()) != NULL)
    {
        newNodes[i] = node->CopyNode(); // also makes a new copy of the node's data
        newNodes[i]->SetNext(NULL);
        newNodes[i]->SetPrev(prev);
        if (prev) prev->SetNext(newNodes[i]);
        prev = newNodes[i];
        ++i;
    }

    if (m_len > 0)
        nlist->SetFirst(newNodes[i]);
    if (m_len > 1)
        nlist->SetLast(newNodes[m_len - 1]);
    return nlist;
}

Примечание: я просто вытащил этот код из своей задницы, чтобы он не тестировался. Надеюсь, это поможет, хотя:)

2 голосов
/ 22 января 2009

Прежде всего, я не совсем уверен, как следует понимать цель упражнения. Насколько глубокой должна быть копия? В решении, подобном вашему, this->next->next и other.next->next были бы тем же самым. Должен ли этот объект также дублироваться? И остальная часть списка? Где это заканчивается? Конечно, можно было бы глубоко скопировать весь список, но я думаю, это было бы довольно неожиданным поведением конструктора копирования отдельного узла.

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

Но вернемся к вашей интерпретации:

Node a(...);
// ... more code that adds a whole list to a
Node b(a);

Есть две проблемы с вашей реализацией. Для одного b->next->prev указывает на a, в то время как я подозреваю, что оно должно указывать на b. Во-вторых, вам нужно подумать о угловых случаях, где a может быть первым или последним узлом в списке.

И на ваш главный вопрос: вы, конечно, правы, где-то вновь созданные объекты должны быть снова delete d. Неважно, если вы просто скопируете узлы prev и next или весь список, я бы сказал, что пользователь этой копии отвечает за удаление всех скопированных узлов снова. Я предполагаю, что с обычным, не скопированным списком, пользователь этого списка будет проходить через все узлы и удалять их вручную один за другим, как только закончит работу со списком. Он не стал бы предполагать, что деструктор одного узла удалит весь список. И то же самое касается копий, они должны вести себя одинаково. Пользователь скопированного материала должен удалить все копии. (На практике у вас, вероятно, будет класс list, который будет выполнять за вас все управление узлами).

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

2 голосов
/ 22 января 2009

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

1 голос
/ 02 февраля 2009

Если каждый узел делает копии узлов, на которые он указывает, вы можете безопасно удалить объекты в деструкторах. Если вы передаете указатели (как это делает конструктор Node (const std :: string & v, Node * p, Node * n)), то вы не «владеете» указателями и не должны их удалять. Если это часть класса связанного списка, то этот класс должен владеть указателями и при необходимости удалять объекты. Вы также можете сделать Node частным подклассом класса связанного списка, чтобы пользователи (или вы сами) не мешали указателям.

Вы также допустили ошибку в рекурсии в своей реализации: конструктор копирования содержит один уровень глубокой копии и вызывает «нормальный» конструктор, который принимает указатели, что делает его поверхностным. Это означает, что ваше глубокое копирование составляет всего один уровень. Он должен повторно вызывать конструктор копирования, что-то вроде этого:

Node(const Node & other) : value(other.value)
{
  prev = new Node(*(other.prev));
  next = new Node(*(other.next));
}

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

...