c++ - 尝试为链表创建复制构造函数时读取访问冲突
问题描述
我在我的链表实现方面遇到问题,我正在尝试创建一个复制构造函数。
// 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;
}
解决方案
通过Node::data
被声明为指针,您的代码负责遵循3/5/0 规则来正确管理data
指针。但它没有这样做。您的复制赋值运算符是对指针本身进行浅拷贝,而不是对它们指向的对象进行深拷贝。
因此,语句会DeleteList()
崩溃delete temp->data;
,因为最终会导致多个Node
s 指向内存中的相同对象,从而破坏了唯一的所有权语义。当一个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_
nullptr
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;
}
或者,直接在类声明中初始化head_
,而不是在构造函数中:
template<typename T>
class List342
{
...
private:
Node<T> *head_ = nullptr; // <--
...
};
推荐阅读
- javascript - mouseUp 事件不会在窗口对象上触发,但 pointerUp 会触发
- javascript - AWS SAM - 调用了测试 lambda,创建了测试 kinesis 事件
- r - 带和不带参数的 R 调用函数
- node.js - WebRtc 远程流
- java - 如何使用验证从命令行运行黄瓜 junit 项目
- javascript - 如何过滤映射/密钥对数组
- javascript - 关于以特定语言学习数据结构和算法的问题(菜鸟问题)
- c# - File.Exists() 对 Program Files 下的某些文件返回 false
- typescript - Firebase CLI 部署警告:Node.js 8 运行时已弃用,将于 2020-12-05 停用
- php - 图片验证错误:头像必须是图片