因此,对于课程我(不断重新发明轮子)编写一堆标准数据结构,如链接列表和地图。我的一切都很好,有点像。插入和删除数据就像魅力一样。
然后主要结束,我的列表被删除,它调用它的dtor并尝试删除其中的所有数据。出于某种原因,这会导致双重免费事件。
所有数据都通过以下方法插入到列表中:
/*
Adds the specified data to the back of the list.
*/
template<typename T, class COMPFUNCTOR>
void List<T, COMPFUNCTOR>::append(T* d)
{
if(tail != NULL)
{//If not an empty list, simply alter the tail.
tail->setNext(new ListNode<T>(d));
tail = tail->getNext();
}
else
{//If an empty list, alter both tail and head.
head = tail = new ListNode<T>(d);
}
size++;
};
/*
Adds a copy of the specified data to the back of the list.
*/
template<typename T, class COMPFUNCTOR>
void List<T, COMPFUNCTOR>::append(const T& d)
{
this->append(new T(d));
};
第一种方法假定它拥有传递给它的数据;第二个复制数据传入其中。现在,对于main:
int main(int argc, char** argv)
{
parser = new Arguments(argc, argv); //Uses a map<char, list<string>>; no direct bugs, insertion works fine.
if(parser->flagSet('f'))
{
printf("%s\n", parser->getArg('f').getFirst().str.c_str());
}
return 0;
}
这导致堆栈转储,用于双重自由事件。 列表析构函数定义如下:
/*
Destroys the List and all data inside it.
*/
template<typename T, class COMPFUNCTOR>
List<T, COMPFUNCTOR>::~List()
{
while(head != NULL)
{
ListNode<T>* tmp = head; //Set up for iteration.
head = head->getNext();
if(tmp->getData() != NULL) //Delete this node's data then the node itself.
delete tmp->getData();
delete tmp;
}
};
如果我注释掉列表析构函数或main的if语句中的代码,程序运行正常。现在,我不确定这个双重删除的来源。
列表在main的末尾被销毁,导致它删除其中的数据;它拥有或被复制到其中,并且只有副本从它出来(唯一的时间列表传递它的数据指针是从列表中删除它)。
显然,在main中的堆栈上创建了一些东西 parser-&GT; GETARG( 'F')getFirst();被称为。
我读到这个, (取消指向解析器的指针) - &gt;(获取链表的引用)。(获取list [std :: string]中第一个元素的副本);
删除指向解析器的指针没什么大不了的(事实上,我应该删除它,oops);删除引用也不应该是一个大问题(只是一个蜜饯指针);并删除第一个元素的副本应该是非问题。我哪里出错了? 的 修改 ListNode的代码如下:
/*
Create a ListNode with the specified neighbor.
*/
template<typename T>
ListNode<T>::ListNode(T* d, ListNode<T>::ListNode* neighbor)
{
data = d;
next = neighbor;
}
/*
Deletes the ListNode.
*/
template<typename T>
ListNode<T>::~ListNode()
{
next = NULL;
if(data != NULL)
delete data;
data = NULL;
}
ListNodes只接受指向它们数据的指针,它们只有在用非空数据指针消亡时才会删除它们的数据。 List本身也只删除了非空的东西。所有删除的数据都设置为NULL。
哦,现在的数据是std :: string,我无法控制它的拷贝构造函数,但我认为它已经正确实现了。
答案 0 :(得分:3)
ListNode析构函数是否也删除了它的数据?
编辑:啊,既然你已经发布了你的源代码 - 注意删除没有设置为null ...所以你的null测试仍然会产生错误。
答案 1 :(得分:1)
你还没有发布它的代码,但我猜测ListNode的析构函数是在你已经在List的析构函数中删除它之后删除它的数据。
答案 2 :(得分:1)
这不是问题的根源,但在删除之前不必检查某些内容是否为NULL。在NULL上调用delete是一个无操作。我不认为你犯了这个错误,但我看到有人写
if(data != NULL)
delete data;
并认为行为是
if(data is properly allocated)
delete data;
当然,这不是真的。
答案 3 :(得分:1)
您是否尊敬the rule of three?当复制应该“管理指针对象”的对象时,通常会发生双重删除。使您的节点不可复制和不可分配,或“正确”定义您自己的复制ctor和赋值运算符。
答案 4 :(得分:1)
查看您的ListNode
课程,显然存在所有权不匹配。设计的一个好的经验法则是,不仅应该通过匹配的去分配来匹配每个分配,而且应该通过代码中的同一层或理想地由相同的对象来执行这些分配。这同样适用于任何需要与发布配对的资源。
很明显,此指南未在此处遵循,这是您的许多问题的根源。
template<typename T>
ListNode<T>::ListNode(T* d, ListNode<T>::ListNode* neighbor)
{
data = d;
next = neighbor;
}
template<typename T>
ListNode<T>::~ListNode()
{
next = NULL;
if(data != NULL)
delete data;
data = NULL;
}
ListNode
删除它未分配的内容。虽然您允许使用null数据成员,但如果您不允许这样做,则会使事情变得更简单。如果您想要一个可选项列表,您可以随时使用具有智能指针类型或boost::optional
的模板。
如果执行此操作,然后确保ListNode
类始终分配和取消分配项目的副本,则可以使数据成员为T
而不是T*
}。这意味着您可以使您的课程如下:
template<typename T>
class ListNode
{
public:
explicit ListNode( const T& d )
: data(d), next()
{
}
T& getData() { return data; }
const T& getData() const { return data; }
ListNode* getNext() const { return next; }
void setNext(ListNode* p) { next = p; }
private:
ListNode* next;
T data;
}
我已经开始使用对现在不能为null的东西的引用。此外,拥有的所有内容现在都是数据成员,而该类所拥有的内容(next
)由指针引用。
在这两个函数中,我希望第二个函数是私有函数,因为析构函数总是假设List
(通过ListNode
)拥有数据指针,因此具有公共函数,不带副本的原始指针有潜在危险。
template<typename T, class COMPFUNCTOR>
void List<T, COMPFUNCTOR>::append(T* d);
template<typename T, class COMPFUNCTOR>
void List<T, COMPFUNCTOR>::append(const T& d);
通过更改上面的ListNode,我们可以实现其中的第二个,而不需要第一个简单的帮助。
template<typename T, class COMPFUNCTOR?
void List<T, COMPFUNCTOR>::append(const T& d)
{
ListNode<T>* newNode = new ListNode<T>(d);
if (!tail)
tail->setNext( newNode );
else
head = newNode;
tail = newNode;
size++;
}
List
的析构函数“walk”仍然类似于你所拥有的,除了不应该尝试手动删除ListNode的拥有数据,当你删除ListNode本身时会自动删除。
注意:我的所有代码都是未经测试的,仅供博览会使用!
答案 5 :(得分:0)
检查您的副本ctor。如果正在按值复制参数,则必须使用no-arg ctor,并且编译器将插入该调用,因此您不会知道它正在发生。
尝试在复制构造函数中放置日志语句,以查看它是否应该被调用。