尝试为链表创建复制构造函数时读取访问冲突

Read access violation when trying to create copy constructor for linked list

我在尝试创建复制构造函数的链表实现方面遇到问题。

// Copy Constructor
List342(const List342& source)
{
    *this = source;
}

List342& operator=(const List342& source)
{
    Node<T>* s_node = nullptr; // Source node
    Node<T>* d_node = nullptr; // Destination node

    if (this == &source)
    {
        return *this;
    }

    // Empty memory on destination
    DeleteList();

    // If the source is empty, return the current object.
    if (source.head_ == nullptr)
    {
        return *this;
    }

    // Copy source node to destination node, then make destination node the head.
    d_node = new Node<T>;
    d_node->data = (source.head_)->data;
    head_ = d_node;
    s_node = (source.head_)->next;

    // Loop and copy the nodes from source
    while (s_node != nullptr)
    {
        d_node->next = new Node<T>;
        d_node = d_node-> next;
        d_node->data = s_node->data;
        s_node = s_node->next;
    }
    return *this;
    
}

出于某种原因,VS Studio 在 d_node->data = s_node->data 行向我抛出读取访问冲突,尽管 while 循环试图阻止这种情况。

罪魁祸首可能在 DeleteList,但由于某些原因,我的其他方法(如打印链表)在调用 DeleteList 后没有任何问题,因为它什么也不打印。我只是想知道这个 DeleteList 方法是否有任何缺陷。

// Delete all elements in the linked list
// Not only do you need to delete your nodes,
// But because the Node data is a pointer, this must be deleted too
void DeleteList()
{
    // Similar to the linkedlist stack pop method except you're running a while
    // loop until you the is empty.
    Node<T>* temp;
    while (head_ != nullptr)
    {
        temp = head_;
        head_ = head_->next;
        // For some reason if I try to delete temp->data, I keep getting symbols not loaded or debug 
        // assertion errors
        // delete temp->data;
        // What I can do here is set it to null
        // Then delete it. This may have to do how uninitialized variables have random memory assigned
        temp->data = nullptr;
        delete temp->data;
        delete temp;
    }
}

这里是 Node 定义:

template <class T>
struct Node
{
   T* data;
   //string* data;
   Node* next;
}

通过将 Node::data 声明为指针,您的代码负责遵循 Rule of 3/5/0 以正确管理 data 指针。但它并没有这样做。您的复制赋值运算符浅复制指针本身,而不是深复制它们指向的对象。

因此,DeleteList()delete temp->data; 语句上崩溃,因为您最终有多个 Node 指向内存中的相同对象,破坏了唯一的所有权语义。当一个 Node 被销毁时,删除它的 data 对象,从它复制的任何其他 Node 现在留下一个指向无效内存的悬空指针。

如果必须为 Node::data 使用指针,则需要使用 new 单独复制构造每个 data 对象,以便 DeleteList() 稍后可以 delete 他们单独,例如:

d_node = new Node<T>;
d_node->data = new T(*(source.head_->data)); // <--
...
d_node->next = new Node<T>;
d_node = d_node->next; 
d_node->data = new T(*(s_node->data)); // <--
...

但是,如果您只是让 Node::data 一开始就不是指针,就不再需要了:

template <class T>
struct Node
{
    T data; //string data;
    Node* next;
}

话虽如此,您的复制构造函数正在调用您的复制赋值运算符,但 head_ 成员尚未初始化(除非已经初始化,而您只是没有显示)。在无效列表上调用 DeleteList() 未定义的行为 。使用复制构造函数(所谓的copy-swap idiom)实现赋值运算符会更安全,而不是相反,例如:

// Copy Constructor
List342(const List342& source)
    : head_(nullptr)
{
    Node<T>* s_node = source.head_;
    Node<T>** d_node = &head_;

    while (s_node)
    {
        *d_node = new Node<T>;
        (*d_node)->data = new T(*(s_node->data));
        // or: (*d_node)->data = s_node->data;
        // if data is not a pointer anymore...
        s_node = s_node->next;
        d_node = &((*d_node)->next);
    }
}

List342& operator=(const List342& source)
{
    if (this != &source)
    {
        List342 temp(source);
        std::swap(head_, temp.head_);
    }
    return *this;
}

但是,如果您确保在调用 operator=,例如:

// Copy Constructor
List342(const List342& source)
    : head_(nullptr) // <--
{
    *this = source;
}

或者:

// Default Constructor
List342()
    : head_(nullptr) // <--
{
}

// Copy Constructor
List342(const List342& source)
    : List342() // <--
{
    *this = source;
}

或者,直接在 class 声明中初始化 head_,根本不在构造函数中:

template<typename T>
class List342
{
...
private:
    Node<T> *head_ = nullptr; // <--
...
};