【问题标题】:Read access violation when trying to create copy constructor for linked list尝试为链表创建复制构造函数时读取访问冲突
【发布时间】:2021-12-26 05:41:11
【问题描述】:

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

// 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-&gt;data = s_node-&gt;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;
}

【问题讨论】:

    标签: c++ pointers linked-list


    【解决方案1】:

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

    因此,DeleteList()delete temp-&gt;data; 语句上崩溃,因为您最终会得到多个 Nodes 指向内存中的相同对象,从而破坏了唯一的所有权语义。当一个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= 之前将head_ 初始化为nullptr,则您显示的复制构造函数可以安全工作,例如:

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

    【讨论】:

    • head_ 在构造函数中被初始化为 nullptr,抱歉没有指出这一点。对于数据,我需要将其设为指针。
    • @DerpyDays head_ 未在您显示的复制构造函数中初始化。每个构造函数同样负责初始化类的所有数据成员。除非您委托构造函数,否则您不会这样做。
    猜你喜欢
    • 1970-01-01
    • 2011-12-10
    • 2019-09-13
    • 1970-01-01
    • 1970-01-01
    • 2020-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多