0
    doubly_linked_list::~doubly_linked_list()
{
    list_item* current = head;
    while (current)
    {
        list_item* next = current->Get_Next();
        delete current;
        current = next;
    }
}

I am writing a dynamic graph in C++ with the usage of a doubly linked list to save the Graph nodes, however my code keeps on failing as it calls the destructor more times than it should be calling it, for example, during my destructor for the graph

    Dynamic_Graph::~Dynamic_Graph()
{
    edges.~doubly_linked_list();
    nodes.~doubly_linked_list();
}

the destructor for the doubly linked list is called more than 2 times, despite it being called explicitly twice.

then i have the function for insertion of an edge:

Graph_Edge* Dynamic_Graph::Insert_Edge(Graph_Node* from, Graph_Node* to)
{
    Graph_Edge* new_edge = new Graph_Edge(from, to);
    from->Get_out_Nodes().List_Insert_After(to, from->Get_out_Nodes().Get_Tail());
    to->Get_in_Nodes().List_Insert_After(from, to->Get_in_Nodes().Get_Tail());
    edges.List_Insert(new_edge);
    return new_edge;
}

after adding the to node to the adjacency list of the from node the code unexplainably calls the destructor for a doubly linked list and deletes this insertion, however i am not sure why.

here are the headers for the graph and the linked list:

class doubly_linked_list
{
private:
    list_item* head;
    list_item* tail;
public:
    doubly_linked_list() { this->head = NULL; this->tail = NULL; }
    doubly_linked_list(list_item* head){ this->head = head; this->tail = NULL; }
    ~doubly_linked_list();
    list_item* Get_Head() { return head; }
    list_item* Get_Tail() { return tail; }
    void Set_Head(list_item* h) { head = h; }
    void List_Insert(list_item* x);
    void List_Insert_After(list_item* x, list_item* y);
    void List_Delete(list_item* x);
};


class Dynamic_Graph
{
protected:
    doubly_linked_list edges;
    doubly_linked_list nodes;
public:
    Dynamic_Graph() { edges = doubly_linked_list(); nodes = doubly_linked_list(); }
    ~Dynamic_Graph();
    Graph_Node* Insert_Node(unsigned node_key);
    void Delete_Node(Graph_Node* node);
    Graph_Edge* Insert_Edge(Graph_Node* from, Graph_Node* to);
    void Delete_Edge(Graph_Edge* edge);
    Rooted_Tree* SCC() const;
    Rooted_Tree* BFS(Graph_Node* source) const;
};

any help would be welcome

EDIT: i removed the calls to the destructors, however i still am getting a destructor call in the insert after function and i am not sure why.

EDIT 2: adding more relevant lines of code:

Graph_Edge* Dynamic_Graph::Insert_Edge(Graph_Node* from, Graph_Node* to)
{
    Graph_Edge* new_edge = new Graph_Edge(from, to);
    from->Get_out_Nodes().List_Insert_After(to, from->Get_out_Nodes().Get_Tail());
    to->Get_in_Nodes().List_Insert_After(from, to->Get_in_Nodes().Get_Tail());
    edges.List_Insert(new_edge);
    return new_edge;
}

this is the function that triggers the error where it access a deleted pointer despite it not supposed to be deleted, it triggers after finishing inserting the "to" node to the "from" node adjacency list.

void doubly_linked_list::List_Insert_After(list_item* x, list_item* y)
{
    if (!y)
    {
        head = x;
        tail = x;
        return;
    }
    if (y == tail)
    {
        tail = x;
    }
    if (y->Get_Next())
    {
        y->Get_Next()->Set_Prev(x);
    }
    x->Set_Next(y->Get_Next());
    x->Set_Prev(y);
    y->Set_Next(x);
}

this function is the insert after function in the doubly linked list.

EDIT 3: i tried to recrating the issue via the smallest amount of possible function, this is what i got:

#pragma once
#include < cstddef >
class List_Item
{
protected:
    List_Item* next;
    List_Item* prev;
public:
    List_Item(): next(NULL), prev(NULL) {}
    List_Item* Get_Next() { return next; }
    List_Item* Get_Prev() { return prev; }
    void Set_Next(List_Item* next) { this->next = next; }
    void Set_Prev(List_Item* prev) { this->prev = prev; }
    List_Item(const List_Item& item) : next(item.next), prev(item.prev){}
    List_Item& operator=(const List_Item& item) { this->next = item.next; this->prev = item.prev; return *this; }
};

class Doubly_Linked_List
{
protected:
    List_Item* head;
    List_Item* tail;
public:
    Doubly_Linked_List() : head(NULL), tail(NULL) {}
    ~Doubly_Linked_List();
    List_Item* Get_Head() { return head; }
    List_Item* Get_Tail() { return tail; }
    void Set_Head(List_Item* h) { head = h; }
    void Set_Tail(List_Item* t) { tail = t; }
    void insert(List_Item* item);
    void insert_after(List_Item* item, List_Item* after);
    Doubly_Linked_List(const Doubly_Linked_List& list) : head(list.head), tail(list.tail) {}
    Doubly_Linked_List& operator=(const Doubly_Linked_List& list) { this->head = list.head; this->tail = list.tail; return *this; }
};
Doubly_Linked_List::~Doubly_Linked_List()
{
    List_Item* item = head;
    while (item)
    {
        List_Item* next = item->Get_Next();
        delete item;
        item = next;
    }
}

void Doubly_Linked_List::insert(List_Item* item)
{
    item->Set_Next(head);
    if (head)
        head->Set_Prev(item);
    if (!head)
        tail = item;
    head = item;
    item->Set_Prev(NULL);
}

void Doubly_Linked_List::insert_after(List_Item* item, List_Item* after)
{
    if (!after)
    {
        head = item;
        tail = item;
        return;
    }
    if (after->Get_Next())
    {
        after->Get_Next()->Set_Prev(item);
    }
    else
    {
        tail = item;
    }
    item->Set_Next(after);
    item->Set_Prev(after);
    after->Set_Next(item);
}


#pragma once
#include "List_Item.h"
#include"Doubly_Linked_List.h"
class Graph_Node :
    public List_Item
{
protected:
    const unsigned key;
    Doubly_Linked_List in_nodes;
    Doubly_Linked_List out_nodes;
public:
    Graph_Node(unsigned key): key(key) {}
    Doubly_Linked_List Get_In_Nodes() { return in_nodes; }
    Doubly_Linked_List Get_Out_Nodes() { return out_nodes; }
};



   class Graph_Edge :
    public List_Item
{
protected:
    Graph_Node* from;
    Graph_Node* to;
public:
    Graph_Edge(Graph_Node* f, Graph_Node* t): from(f), to(t) {}
    Graph_Node* Get_From() { return from; }
    Graph_Node* Get_To() { return to; }
};

int main()
{

    unsigned node_key = 1;
    Graph_Node* from = new Graph_Node(node_key++);
    Graph_Node* to = new Graph_Node(node_key++);
    from->Get_Out_Nodes().insert_after(to, from->Get_Out_Nodes().Get_Tail());
    to->Get_In_Nodes().insert_after(from, to->Get_In_Nodes().Get_Tail());
}
Ziv Riger
  • 39
  • 3

1 Answers1

1

You are not supposed to call destructors manually. They are called automatically for you.

You only need to use delete (which also calls the destructor) and only on objects that you have actually created with a call to new. Every other object will be destroyed automatically when the end of its scope is reached and its destructor will then be called automatically. This is also true for members of classes.

The only exception here is if you used a placement-new to create the object or if you intend to reuse its storage. But you probably don't intend to do something like this.

Remove the destructor of Dynamic_Graph completely. It is not needed.


Your class doubly_linked_list violates the rule of 0/3/5. The rule says that if you define a custom destructor, then you should also define a custom copy(/move) constructor and copy(/move) assignment operator.

If you violate this rule and you happen to make an implicit or explicit copy of an object of that class, then you are likely going to have undefined behavior because the destructors will be called twice and try to delete memory twice.


After question edit with a full example:

In the code you are showing now the destructor for Doubly_Linked_List is called four times (see https://godbolt.org/z/KmStwy). These calls are because Get_In_Nodes and Get_Out_Nodes return Doubly_Linked_List lists by-value as copies of the class members of Graph_Node. Since you call these functions four times in main, there are four Doubly_Linked_List temporary objects that need to be destructed (which the compiler does automatically).

You did not properly implement Doubly_Linked_List(const Doubly_Linked_List& list) (and the copy assignment operator) to actually copy the whole list, so your program still has undefined behavior. You need to implement the copy operations in such a way that the whole list is being deep copied, otherwise the destructor call of both the originial and the copy will delete the same nodes.

Alternatively you can define the copy operations as deleted:

Doubly_Linked_List(const Doubly_Linked_List& list) : head(list.head), tail(list.tail) = delete
Doubly_Linked_List& operator=(const Doubly_Linked_List& list) = delete;

in which case the compiler will not allow copying and give a compile-time error if you try to copy. But your current implementation is just as broken as it was before. You basically just copied what the implicit copy operations do. Read the link about the rule of 0/3/5 again and carefully since it is extremely important to avoid undefined behavior.

The fact that the destructor is called multiple times is not by itself a problem. That is normal if copies are involved. The problem is only that your class copy operations are broken. If you don't want to create copies in the Get_In_Nodes and Get_Out_Nodes though, then return by-reference instead.


In general you should not use new/delete in the way that you are doing in modern C++. Instead you can use std::make_unique and std::unique_ptr for owning pointers and if you do so, then none of your shown classes will need custom destructors (at least not custom destructors that have effective behavior different from the implicit one) and you can always use the rule of 0, meaning that you don't need to implement any of the copy operations either.

And if this is anything but a learning exercise, you shouldn't write your own list in the first place. std::list works fine and is almost surely superior to what you are coming up with.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • thanks for the correction on calling the destructors. – Ziv Riger Jan 26 '20 at 13:37
  • however its still calls the destructor in the insert after function i think – Ziv Riger Jan 26 '20 at 13:38
  • @ZivRiger You think or you know? Have you used a debugger to find out where is destructor called? – Daniel Langr Jan 26 '20 at 13:41
  • yea i am using the debugger, after it inserted the "to" node to the adjacency list of the from node, it called the destructor and deleted the node from the list – Ziv Riger Jan 26 '20 at 13:42
  • @ZivRiger See updated answer. It is hard to see from incomplete code. But I suspect that you are copying your `doubly_linked_list` somewhere implicitly. It will then call the destructor for the copy as well. Your class violates the rule of 0/3/5 and therefore this will lead to undefined behavior. Such copies/moves are not a problem and there is no issue with the destructor being called multiple times. You just have to implement the special member functions correctly. – walnut Jan 26 '20 at 13:44
  • i tried to implement the copy constructor for the doubly linked list, however the issue still persists and when it ends adding the "to" node to "from" node's adjacency list it has still called the destructor, twice actually, and deleted the head of the adjacency list. – Ziv Riger Jan 26 '20 at 13:50
  • @ZivRiger Then you need to provide a complete [repro] of the issue. There are too many unknowns in your posted code to give a specific answer for that. – walnut Jan 26 '20 at 13:53
  • @walnut i tried to reproduce it like you asked, the 3rd edit has all the code that i have written to reproduce it. – Ziv Riger Jan 26 '20 at 15:36
  • @ZivRiger _it called the destructor_ — What is _it_? With debugger, you can easily find out exactly from where is destructor called. Just put a breakpoint in it and examine the call stack. Also, highly recommend to use `std::list` instead of you custom one. – Daniel Langr Jan 27 '20 at 06:46