首页 > 解决方案 > 尝试为链表创建复制构造函数时读取访问冲突

问题描述

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

// 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;
    
}

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

罪魁祸首可能在于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;
}

标签: c++pointerslinked-list

解决方案


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

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

如果您必须使用指针Node::data,那么您需要单独使用复制构造每个data对象,new以便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;
}

但是,如果您确保在调用之前进行初始化,则可以使您显示的复制构造函数安全地工作,例如:head_nullptroperator=

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

或者:

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

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

或者,直接在类声明中初始化head_,而不是在构造函数中:

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

推荐阅读