0

Using this template class works perfectly fine when main operates with constructed variables of type dlring, yet my goal is to allow dynamic allocation, so I can handle a non-predefined number of doubly linked circular lists to allow usage of such functions as:

  • Splitting a list into two by either using a node position (via
    iteration) or value entry.
  • Same goes for linking two lists into one with a single head/tail pair.
  • Node exporting from one list (instance) to another.
  • Etc.

I'm pretty much sure there is an elegant workaround which is simply not known by me yet, but I don't think it's nice to come up with a question for the community if you didn't struggle enough to resolve. (checked google

So with that goals I'm supposed to dynamically allocate memory (via constructor calls) using some kind of pointer-to-pointer, AFAIK. If there is a smarter way to implement these, please let me know. My solution attempt is given in the end of this snippet. Feel free to criticize all of the below.

Doubly linked circular list class header (simplified)

template <typename T>
class dlring
{
    struct node
    {
        T data;
        node* prev;
        node* next;
        node(T t, node* p, node* n) : data(t), prev(p), next(n) {}
    };
    node* head;
    node* tail;
public:
    dlring():head(nullptr), tail(nullptr){}
    bool empty() const { return ( !head || !tail ); }
//operator bool() const { return !empty(); }
    void Push(T);
    T pop_back();
    ~dlring()
    {
        while(head)
        {
            node* temp(head);
            head=head->next;
            delete temp;
        }
    }
};

Should I use the commented out operator bool overload?

pop_back and Push methods:

template <typename T>
void dlring<T>::Push(T data)
{
    head = new node(data, tail, head); 
    if( head->next )
    {
        head->next->prev = head;
        tail->next = head;
    }
    if( empty() )
    {
        tail = head;
        head->next=tail;
        head->prev=tail;
        tail->next=head;
        tail->prev=head;
    }
}
template<typename T>
T dlring<T>::pop_back()
{
    if( empty() )
        std::cout<<"List empty";
    node* temp(tail);
    T data( tail->data );
    tail = tail->prev ;
    if (tail != temp)
    {
        tail->next->next = head; 
        head->prev = tail;
    }
    else
    {
        head = nullptr;
        tail = nullptr;
    }
    delete temp;
    temp = nullptr;
    return data;
}

My attempt doesn't have the right behaviour: When I'm trying to show all the lists via a iteration the code fails, segfaulting on head->data access attempt of dlist[0], where 0 is an iteration of k. Here is the snippet:

   int main()
    {
    int k;
      std::cout<<"Rings count?"<<std::endl;
      std::cin>>k;
        dlring<int>* dlist = new dlring<int>[k]; //I suppose I'm allocating *k*
     //dlring<int> elements. this line is not confirmed to call the constructor.
    (dlist[0]).Push(10);
    (dlist[0]).Push(13);
    (dlist[1]).Push(99);
    /*{
    while(!dlist[0].empty())
    std::cout<<(dlist[0]).pop_back()<<" ";
    std::cout<<std::endl;
    while(!dlist[1].empty())
    std::cout<<(dlist[1]).pop_back()<<" ";
    }*/
    //this section works perfectly fine, while this
      for(int i=0;i<k;i++)
      {
        while(!dlist[k].empty())
        std::cout<<(dlist[k]).pop_back()<<" ";
        std::cout<<std::endl;
      }
    //is causing a segmentation fault while attempting to access dlist[*0*].tail->data.
    std::cout<<(dlist[0]).head->data;
    //line was checked and is confirmed to be functional, 
    //I suppose dlist[variable] has some trick I don't know yet.
    //what I wish to look like an instance call would be *
    return 0;
    }

Best regards. Again, feel free to criticize any of my code/logics.

  • Found this while roaming in google: // Overloading operators // Selector T* operator->() { return m_obj; } // adress access T& operator* () { return *m_obj; } Is it of any help? – Глушков Макс May 12 '15 at 23:28
  • 1
    I think the set-up of the tail in `pop_back` is incorrect: `tail->next->next = head;` At this point, `tail` already points to the *new tail*, so I'd set `tail->next = head;` – dyp May 12 '15 at 23:28
  • @dyp Yes it does, yet to make *new tail*->next=head I reference to *old tail* via *new* tail->next. Hope I ain't mistaken. – Глушков Макс May 12 '15 at 23:32
  • Maybe I'm misunderstanding the purpose of `tail`, but I just don't get it: Isn't `head->prev == tail && tail->next == head` guaranteed for all nonempty lists (outside of member functions, of course)? If so, why do you need two data members? – dyp May 12 '15 at 23:36
  • If you sacrifice a bit of space and you use a header node (that il not will be part of the set), then your code will be much simpler and faster, because the extremes (head and tail) are accessible from the header node. And you do not have to worry about boundary conditions (empty list for push() or list with one element for pop_back()). Moreover, your destructor is also simpler and safer if you use pop_back () until the list becomes empty. – lrleon May 12 '15 at 23:55
  • @dyp I ain't stating neither my implementation is error-prone, nor 100% correct. I'm quite too tired to put my brains to work, I shall reply when I review those lines of mine properly. Anyone, feel free to come up with an opinion regarding this, please. – Глушков Макс May 12 '15 at 23:55
  • @Irleon do you mean by this the master (sentinel) node which consists of two pointers for head/tail? Implementation would change quite a lot if so. Did I get you right? – Глушков Макс May 13 '15 at 00:01
  • @dyp it is guaranteed theoretically, yet while popping/pushing we are modifying the adress at which the tail/head node is present, while clearing/pushing another node instead. Thus, head or tail adress changes and we are supposed to change the links accordingly. At this point it would be perfect someone to tell me I'm completely mistaken and fix my vision of these pointers properly. – Глушков Макс May 13 '15 at 00:17
  • Yet, to be fair, we quite derived from the point that the list is functional, but I'm *segfaulting* in the attempt to return data using a (dlist[k]).pop_back() call, where k is supposed to be a dlist_instance_index. While the same call using an integer value instead of *the same* adress reference via variable k **runs fine**. – Глушков Макс May 13 '15 at 00:22

1 Answers1

0
  for(int i=0;i<k;i++)
  {
    while(!dlist[k].empty())
    std::cout<<(dlist[k]).pop_back()<<" ";
    std::cout<<std::endl;
  }

Is not using the iterator i. It should be:

  for(int i=0;i<k;i++)
  {
    while(!dlist[i].empty())
    std::cout<<(dlist[i]).pop_back()<<" ";
    std::cout<<std::endl;
  }

Since dlist is an array of size k, the original code produces an out-of-bounds access.


Since the aforementioned loop makes every list in the dlist array empty, the head of all of them will be a null pointer. Note that you shouldn't be able to access it at all, since it is a private member. If you do, you'll get an segfault when dereferencing it:

std::cout<<(dlist[0]).head->data;

If this compiles, at this point in the program, dlist[0].head == nullptr, therefore segfault.

Also note that you're leaking memory since you're not releasing the dynamically allocated dlist. Append to the end of you program:

delete[] dlist;

With those changes, I don't get any segfaults, or issues, or reports from clang's address sanitizer.


Another issue (that doesn't manifest in your main) is the set-up of tail in pop_back. I'll try to use some ASCII art for illustration. A box

   D ->
<- D

denotes a node with Data, a next pointer and a prev pointer. All arrows are pointers.

A nonempty list:

   +-----------------------------+
   v                             |
   D ->    D -> ..    D ->    D -+
+- D    <- D    .. <- D    <- D
|                             ^
+-----------------------------+
   ^                          ^
   |head                      |tail

head->prev points to the same object as tail, and similarly, tail->next points to the same object as head.

Now, an "animation" of the pop_back function.

template<typename T>
T dlring<T>::pop_back()
{
    if( empty() )
        std::cout<<"List empty";
    node* temp(tail);

    /*
       +-----------------------------+
       v                             |
       D ->    D -> ..    D ->    D -+
    +- D    <- D    .. <- D    <- D
    |                             ^
    +-----------------------------+
       ^                          ^
       |head                      |tail
                                  |temp
    */

    T data( tail->data );
    tail = tail->prev ;

    /*
       +-----------------------------+
       v                             |
       D ->    D -> ..    D ->    D -+
    +- D    <- D    .. <- D    <- D
    |                             ^
    +-----------------------------+
       ^                  ^       ^
       |head              |tail   |temp
    */

    if (tail != temp)
    {
        tail->next->next = head; 

        /*
           +-----------------------------+
           v                             |
           D ->    D -> ..    D ->    D -+  (A)
        +- D    <- D    .. <- D    <- D
        |                             ^
        +-----------------------------+
           ^                  ^       ^
           |head              |tail   |temp

        The pointer (A) is what is changed when writing to tail->next->next.
        Yes, nothing has actually changed in the list!
        */

        head->prev = tail;

        /*
           +-----------------------------+
           v                             |
           D ->    D -> ..    D ->    D -+
        +- D    <- D    .. <- D    <- D
        |                     ^
        +---------------------+
           ^                  ^       ^
           |head              |tail   |temp
        */

    }
    else
    {
        head = nullptr;
        tail = nullptr;
    }
    delete temp;

    /*
       D ->    D -> ..    D ->
    +- D    <- D    .. <- D
    |                     ^
    +---------------------+
       ^                  ^       ^
       |head              |tail   |temp
    */

    temp = nullptr;
    return data;
}

Note that in the last "picture", tail->next is an invalid pointer.

Instead, it should be:

    if (tail != temp)
    {
        tail->next = head; 

        /*
           +---------------------+-------+
           v                     |       |
           D ->    D -> ..    D -+    D -+
        +- D    <- D    .. <- D    <- D
        |                             ^
        +-----------------------------+
           ^                  ^       ^
           |head              |tail   |temp

After deletion of temp (no further changes), it will look like this:

        /*
           +---------------------+
           v                     |
           D ->    D -> ..    D -+
        +- D    <- D    .. <- D
        |                     ^
        +---------------------+
           ^                  ^       ^
           |head              |tail   |temp

Last but not least, please be aware that you're violating the Rule of Three.

Community
  • 1
  • 1
dyp
  • 38,334
  • 13
  • 112
  • 177
  • please have a glimpse at my main, i get the segfault error trying to T data (tail->data) access invoking pop_back via (dlist[k=0]), but (dlist[0]) runs fine. Bear in mind tail->data refers to the "new" tail achieved (from either the end of popback or first attempt to popback, while both are legal). P.S.: Finally, I managed to write this comment in a readable way, if only i could rate up your efforts. Many thanks for the attention. – Глушков Макс May 13 '15 at 01:07
  • @ГлушковМакс Ouch, I didn't see the iteration variable mishap in your `main`. Prepended to my answer. – dyp May 13 '15 at 01:33
  • I'm kind of brainhurting atm. Do we have a solution? – Глушков Макс May 13 '15 at 01:39
  • @ГлушковМакс I think so. With two additional changes, I'm rather confident that the program should run fine. Please see the beginning of my answer. – dyp May 13 '15 at 01:46
  • Well what we've got here is a nice implementation and one sad tired programmer. Thank you a lot! xD UPD: all working, checked all of your answer statements, great job done. Tiredness-related issues all over my code... – Глушков Макс May 13 '15 at 02:13
  • %) superb, I wasn't even hoping to meet such enthusiasts in here. PEOPLE VOTE DYP UP PLEASE, HE WAS HARD CHECKING FOR MY RANDOM ERRORS FOR 2 HOURS. – Глушков Макс May 13 '15 at 02:22