Circular Linked List implementation in C++

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
0
down vote

favorite












I want to improve this code.



#include <iostream>
#include <utility>

template <class T>
class circular_linked_list

struct Node

T data;
Node * next;
Node(T value) : data(std::move(value)), next(nullptr)
;
Node *head;

public:
circular_linked_list() : head(nullptr)
circular_linked_list(const circular_linked_list& cll) = delete; //copy constructor
circular_linked_list(circular_linked_list&& cll) = delete; //move constructor
circular_linked_list& operator=(const circular_linked_list& cll) = delete; //copy assignment
circular_linked_list& operator=(circular_linked_list&& cll) = delete; //move assignment
~circular_linked_list();
void insert_at_begin(T);
void insert_at_end(T);
void delete_node(T);
void print_list();

private:
struct Node *search(T value)

Node *node = head;
while(node->next != head)

if(node->data == value)
return node;

node = node->next;

std::cerr << "No such element in the List n";
return nullptr;

;

template <class T>
void circular_linked_list<T>::insert_at_begin(T value)

Node *node = new Node(std::move(value));
if(head == nullptr)

node->next = node;
head = node;
return;

Node *tmp = head;
while(tmp->next != head)

tmp = tmp->next;

tmp->next = node;
node->next = head;
head = node;


template <class T>
void circular_linked_list<T>::insert_at_end(T value)

Node *node = new Node(std::move(value));
if(head == nullptr)

node->next = node;
head = node;
return;

Node *tmp = head;
while(tmp->next != head)

tmp = tmp->next;

tmp->next = node;
node->next = head;


template <class T>
void circular_linked_list<T>::delete_node(T value)

Node *node = search(value);
Node *tmp = head;
Node *tail = head;
while(tail->next != head)

tail = tail->next;


if(tmp == node)

head = tmp->next; //deleting first element
tail->next = head;

else

while(node != nullptr)

if(tmp->next == node)

tmp->next = node->next;
return;

tmp = tmp->next;


delete tmp;


template <class T>
void circular_linked_list<T>::print_list()

Node *tmp = head;
while(tmp->next != head)

std::cout << tmp->data << ' ';
tmp = tmp->next;

std::cout << tmp->data << 'n';


template <class T>
circular_linked_list<T>::~circular_linked_list()

Node *tmp = nullptr;
Node *tail = head;
while(tail->next != head)

tail = tail->next;

tail->next = nullptr;

while(head != nullptr)

tmp = head;
head = head->next;
delete tmp;



int main()

circular_linked_list<int> cll1;
cll1.insert_at_end(2);
cll1.insert_at_end(3);
cll1.insert_at_end(4);
cll1.insert_at_begin(1);
cll1.insert_at_begin(0);
cll1.print_list();
cll1.delete_node(2);
cll1.print_list();







share|improve this question















  • 2




    That isn't a circular linked list... By definition, something circular neither begins nor ends. Besides, I can't see why you would disallow copy or move operations on a container.
    – papagaga
    Jan 29 at 9:40






  • 1




    @papagaga: Disagree. Just because it is circular does not mean it does not have a beginning or end. It does mean the beginning and end are connected though. Also a quick read of the insert operations does seem to indicate that the OP is creating a circular list.
    – Martin York
    Jan 29 at 17:56











  • The down votes on this are absolutely wrong. Shame on you people.
    – Martin York
    Jan 29 at 17:59










  • @MartinYork, although I agree that closing was too extreme, downvoting seems legit to me. Author could include design decisions and their train of thought. The only context that tests reflect is that there is no context. If there is anything I learnt from you, then it is about always paying attention to context.
    – Incomputable
    Jan 29 at 19:32

















up vote
0
down vote

favorite












I want to improve this code.



#include <iostream>
#include <utility>

template <class T>
class circular_linked_list

struct Node

T data;
Node * next;
Node(T value) : data(std::move(value)), next(nullptr)
;
Node *head;

public:
circular_linked_list() : head(nullptr)
circular_linked_list(const circular_linked_list& cll) = delete; //copy constructor
circular_linked_list(circular_linked_list&& cll) = delete; //move constructor
circular_linked_list& operator=(const circular_linked_list& cll) = delete; //copy assignment
circular_linked_list& operator=(circular_linked_list&& cll) = delete; //move assignment
~circular_linked_list();
void insert_at_begin(T);
void insert_at_end(T);
void delete_node(T);
void print_list();

private:
struct Node *search(T value)

Node *node = head;
while(node->next != head)

if(node->data == value)
return node;

node = node->next;

std::cerr << "No such element in the List n";
return nullptr;

;

template <class T>
void circular_linked_list<T>::insert_at_begin(T value)

Node *node = new Node(std::move(value));
if(head == nullptr)

node->next = node;
head = node;
return;

Node *tmp = head;
while(tmp->next != head)

tmp = tmp->next;

tmp->next = node;
node->next = head;
head = node;


template <class T>
void circular_linked_list<T>::insert_at_end(T value)

Node *node = new Node(std::move(value));
if(head == nullptr)

node->next = node;
head = node;
return;

Node *tmp = head;
while(tmp->next != head)

tmp = tmp->next;

tmp->next = node;
node->next = head;


template <class T>
void circular_linked_list<T>::delete_node(T value)

Node *node = search(value);
Node *tmp = head;
Node *tail = head;
while(tail->next != head)

tail = tail->next;


if(tmp == node)

head = tmp->next; //deleting first element
tail->next = head;

else

while(node != nullptr)

if(tmp->next == node)

tmp->next = node->next;
return;

tmp = tmp->next;


delete tmp;


template <class T>
void circular_linked_list<T>::print_list()

Node *tmp = head;
while(tmp->next != head)

std::cout << tmp->data << ' ';
tmp = tmp->next;

std::cout << tmp->data << 'n';


template <class T>
circular_linked_list<T>::~circular_linked_list()

Node *tmp = nullptr;
Node *tail = head;
while(tail->next != head)

tail = tail->next;

tail->next = nullptr;

while(head != nullptr)

tmp = head;
head = head->next;
delete tmp;



int main()

circular_linked_list<int> cll1;
cll1.insert_at_end(2);
cll1.insert_at_end(3);
cll1.insert_at_end(4);
cll1.insert_at_begin(1);
cll1.insert_at_begin(0);
cll1.print_list();
cll1.delete_node(2);
cll1.print_list();







share|improve this question















  • 2




    That isn't a circular linked list... By definition, something circular neither begins nor ends. Besides, I can't see why you would disallow copy or move operations on a container.
    – papagaga
    Jan 29 at 9:40






  • 1




    @papagaga: Disagree. Just because it is circular does not mean it does not have a beginning or end. It does mean the beginning and end are connected though. Also a quick read of the insert operations does seem to indicate that the OP is creating a circular list.
    – Martin York
    Jan 29 at 17:56











  • The down votes on this are absolutely wrong. Shame on you people.
    – Martin York
    Jan 29 at 17:59










  • @MartinYork, although I agree that closing was too extreme, downvoting seems legit to me. Author could include design decisions and their train of thought. The only context that tests reflect is that there is no context. If there is anything I learnt from you, then it is about always paying attention to context.
    – Incomputable
    Jan 29 at 19:32













up vote
0
down vote

favorite









up vote
0
down vote

favorite











I want to improve this code.



#include <iostream>
#include <utility>

template <class T>
class circular_linked_list

struct Node

T data;
Node * next;
Node(T value) : data(std::move(value)), next(nullptr)
;
Node *head;

public:
circular_linked_list() : head(nullptr)
circular_linked_list(const circular_linked_list& cll) = delete; //copy constructor
circular_linked_list(circular_linked_list&& cll) = delete; //move constructor
circular_linked_list& operator=(const circular_linked_list& cll) = delete; //copy assignment
circular_linked_list& operator=(circular_linked_list&& cll) = delete; //move assignment
~circular_linked_list();
void insert_at_begin(T);
void insert_at_end(T);
void delete_node(T);
void print_list();

private:
struct Node *search(T value)

Node *node = head;
while(node->next != head)

if(node->data == value)
return node;

node = node->next;

std::cerr << "No such element in the List n";
return nullptr;

;

template <class T>
void circular_linked_list<T>::insert_at_begin(T value)

Node *node = new Node(std::move(value));
if(head == nullptr)

node->next = node;
head = node;
return;

Node *tmp = head;
while(tmp->next != head)

tmp = tmp->next;

tmp->next = node;
node->next = head;
head = node;


template <class T>
void circular_linked_list<T>::insert_at_end(T value)

Node *node = new Node(std::move(value));
if(head == nullptr)

node->next = node;
head = node;
return;

Node *tmp = head;
while(tmp->next != head)

tmp = tmp->next;

tmp->next = node;
node->next = head;


template <class T>
void circular_linked_list<T>::delete_node(T value)

Node *node = search(value);
Node *tmp = head;
Node *tail = head;
while(tail->next != head)

tail = tail->next;


if(tmp == node)

head = tmp->next; //deleting first element
tail->next = head;

else

while(node != nullptr)

if(tmp->next == node)

tmp->next = node->next;
return;

tmp = tmp->next;


delete tmp;


template <class T>
void circular_linked_list<T>::print_list()

Node *tmp = head;
while(tmp->next != head)

std::cout << tmp->data << ' ';
tmp = tmp->next;

std::cout << tmp->data << 'n';


template <class T>
circular_linked_list<T>::~circular_linked_list()

Node *tmp = nullptr;
Node *tail = head;
while(tail->next != head)

tail = tail->next;

tail->next = nullptr;

while(head != nullptr)

tmp = head;
head = head->next;
delete tmp;



int main()

circular_linked_list<int> cll1;
cll1.insert_at_end(2);
cll1.insert_at_end(3);
cll1.insert_at_end(4);
cll1.insert_at_begin(1);
cll1.insert_at_begin(0);
cll1.print_list();
cll1.delete_node(2);
cll1.print_list();







share|improve this question











I want to improve this code.



#include <iostream>
#include <utility>

template <class T>
class circular_linked_list

struct Node

T data;
Node * next;
Node(T value) : data(std::move(value)), next(nullptr)
;
Node *head;

public:
circular_linked_list() : head(nullptr)
circular_linked_list(const circular_linked_list& cll) = delete; //copy constructor
circular_linked_list(circular_linked_list&& cll) = delete; //move constructor
circular_linked_list& operator=(const circular_linked_list& cll) = delete; //copy assignment
circular_linked_list& operator=(circular_linked_list&& cll) = delete; //move assignment
~circular_linked_list();
void insert_at_begin(T);
void insert_at_end(T);
void delete_node(T);
void print_list();

private:
struct Node *search(T value)

Node *node = head;
while(node->next != head)

if(node->data == value)
return node;

node = node->next;

std::cerr << "No such element in the List n";
return nullptr;

;

template <class T>
void circular_linked_list<T>::insert_at_begin(T value)

Node *node = new Node(std::move(value));
if(head == nullptr)

node->next = node;
head = node;
return;

Node *tmp = head;
while(tmp->next != head)

tmp = tmp->next;

tmp->next = node;
node->next = head;
head = node;


template <class T>
void circular_linked_list<T>::insert_at_end(T value)

Node *node = new Node(std::move(value));
if(head == nullptr)

node->next = node;
head = node;
return;

Node *tmp = head;
while(tmp->next != head)

tmp = tmp->next;

tmp->next = node;
node->next = head;


template <class T>
void circular_linked_list<T>::delete_node(T value)

Node *node = search(value);
Node *tmp = head;
Node *tail = head;
while(tail->next != head)

tail = tail->next;


if(tmp == node)

head = tmp->next; //deleting first element
tail->next = head;

else

while(node != nullptr)

if(tmp->next == node)

tmp->next = node->next;
return;

tmp = tmp->next;


delete tmp;


template <class T>
void circular_linked_list<T>::print_list()

Node *tmp = head;
while(tmp->next != head)

std::cout << tmp->data << ' ';
tmp = tmp->next;

std::cout << tmp->data << 'n';


template <class T>
circular_linked_list<T>::~circular_linked_list()

Node *tmp = nullptr;
Node *tail = head;
while(tail->next != head)

tail = tail->next;

tail->next = nullptr;

while(head != nullptr)

tmp = head;
head = head->next;
delete tmp;



int main()

circular_linked_list<int> cll1;
cll1.insert_at_end(2);
cll1.insert_at_end(3);
cll1.insert_at_end(4);
cll1.insert_at_begin(1);
cll1.insert_at_begin(0);
cll1.print_list();
cll1.delete_node(2);
cll1.print_list();









share|improve this question










share|improve this question




share|improve this question









asked Jan 29 at 9:30









coder

911425




911425







  • 2




    That isn't a circular linked list... By definition, something circular neither begins nor ends. Besides, I can't see why you would disallow copy or move operations on a container.
    – papagaga
    Jan 29 at 9:40






  • 1




    @papagaga: Disagree. Just because it is circular does not mean it does not have a beginning or end. It does mean the beginning and end are connected though. Also a quick read of the insert operations does seem to indicate that the OP is creating a circular list.
    – Martin York
    Jan 29 at 17:56











  • The down votes on this are absolutely wrong. Shame on you people.
    – Martin York
    Jan 29 at 17:59










  • @MartinYork, although I agree that closing was too extreme, downvoting seems legit to me. Author could include design decisions and their train of thought. The only context that tests reflect is that there is no context. If there is anything I learnt from you, then it is about always paying attention to context.
    – Incomputable
    Jan 29 at 19:32













  • 2




    That isn't a circular linked list... By definition, something circular neither begins nor ends. Besides, I can't see why you would disallow copy or move operations on a container.
    – papagaga
    Jan 29 at 9:40






  • 1




    @papagaga: Disagree. Just because it is circular does not mean it does not have a beginning or end. It does mean the beginning and end are connected though. Also a quick read of the insert operations does seem to indicate that the OP is creating a circular list.
    – Martin York
    Jan 29 at 17:56











  • The down votes on this are absolutely wrong. Shame on you people.
    – Martin York
    Jan 29 at 17:59










  • @MartinYork, although I agree that closing was too extreme, downvoting seems legit to me. Author could include design decisions and their train of thought. The only context that tests reflect is that there is no context. If there is anything I learnt from you, then it is about always paying attention to context.
    – Incomputable
    Jan 29 at 19:32








2




2




That isn't a circular linked list... By definition, something circular neither begins nor ends. Besides, I can't see why you would disallow copy or move operations on a container.
– papagaga
Jan 29 at 9:40




That isn't a circular linked list... By definition, something circular neither begins nor ends. Besides, I can't see why you would disallow copy or move operations on a container.
– papagaga
Jan 29 at 9:40




1




1




@papagaga: Disagree. Just because it is circular does not mean it does not have a beginning or end. It does mean the beginning and end are connected though. Also a quick read of the insert operations does seem to indicate that the OP is creating a circular list.
– Martin York
Jan 29 at 17:56





@papagaga: Disagree. Just because it is circular does not mean it does not have a beginning or end. It does mean the beginning and end are connected though. Also a quick read of the insert operations does seem to indicate that the OP is creating a circular list.
– Martin York
Jan 29 at 17:56













The down votes on this are absolutely wrong. Shame on you people.
– Martin York
Jan 29 at 17:59




The down votes on this are absolutely wrong. Shame on you people.
– Martin York
Jan 29 at 17:59












@MartinYork, although I agree that closing was too extreme, downvoting seems legit to me. Author could include design decisions and their train of thought. The only context that tests reflect is that there is no context. If there is anything I learnt from you, then it is about always paying attention to context.
– Incomputable
Jan 29 at 19:32





@MartinYork, although I agree that closing was too extreme, downvoting seems legit to me. Author could include design decisions and their train of thought. The only context that tests reflect is that there is no context. If there is anything I learnt from you, then it is about always paying attention to context.
– Incomputable
Jan 29 at 19:32











1 Answer
1






active

oldest

votes

















up vote
3
down vote



accepted










Design



With a singly linked list it is O(n) to find the end (assuming you store the beginning). So it is often nice to track both the beginning and end of the list inside the list object.



Personally I would have gone with a doubly linked list (next/prev). That way you can store just the head but finding the tail (on a circular list) is simply getting the link before the head.



// Head of a list
list->head

// Tail of a list (doubly linked)
list->head->prev;


Another thing you should consider is a sentinel link. This is a link in the list that does not contain data but is always there. Note: The list is empty when it only contains the sentinel element. The advantage to using a sentinel node is that you don't have to worry about nullptr when traversing the list. This drastically reduces the amount of code you need to write.



So from that long description two changes I would make.



  1. Doubly linked list next/prev

  2. Add a sentinel node.

Code Review



Move and Copy



I see you deleted the copy/move operations. Fine if you are still working through it. But in the end you will need to implement these. These are actually quite simple when you learn the tricks (idioms).



As a simple helpping hand:



circular_linked_list(const circular_linked_list& cll)
// This you need to implement in full.
// I leave the details to you (but a full deep copy of the list is needed).

// The assignment operator is easy.
// It is implemented in terms of the copy constructor.
// And the swap operation.
circular_linked_list& operator=(const circular_linked_list& cll)

circular_linked_list copy(cll); // Make a copy.
swap(copy); // Call the swap method.
return *this;
// Small improvement can be made (look up copy and swap idiom).



// Move is easily implemented using a swap.
circular_linked_list(circular_linked_list&& cll) noexcept // dont forget the noexcept
// Dont forget to initialize the members correctly.

// Swapping implements the requirements of move.
// It also provided deferred deletion (which is a nice side affect)
// allowing potential reuse of memory. But usually the object
// will be correctly and normally destroyed.
// It also provides the strong exception guarantee.
swap(cll);


circular_linked_list& operator=(circular_linked_list&& cll) noexcept

swap(cll);
return *this;


void swap(circular_linked_list& other) noexcept

using std::swap;
// Implement the swap of all members for other into this.


friend void swap(circular_linked_list& lhs, circular_linked_list& rhs)

lhs.swap(rhs);



Don't need an explicit constructor here.



 struct Node

T data;
Node * next;
Node(T value) : data(std::move(value)), next(nullptr)
;


If you use the brace initializer you don't need an explicit initializer as long as you use put the members in order.



 Node* node = new Node 12, nullptr ;


But you may want to explore some alternative constructors.



 // copy Cosntruct node.
Node(T const& value); // Pass by const reference and copy in
Node(T&& value); // Pass by r-value ref and move in
template<typename... P>
Node(P&&... args); // Pass using the arguments that constructor
// T allowing construction in place.


Printing



This is fine.



 void print_list();


But in C++ we usually print using the operator<<. So it is nice to define this operator for your class. Also std::cout is not the only stream you can print too (Files/Strings are two that pop out but lot of things are emulated like streams).



 void print_list(std::ostream& str = std::cout) const;
friend std::ostream& operator<<(std::ostream& str, circular_linked_list const& data)

data.print_list(str);
return str;



Perfectly good search.



 struct Node *search(T value)
{
Node *node = head;
while(node->next != head)

if(node->data == value)
return node;

node = node->next;



But this may have been easier to write as a for loop:



 for(Node *node = head; node->next != head; node = node->next)

if(node->data == value)
return node;




Insert



Both insert operations have a lot of shared code.
It would be better if you factor out the shared code into a separate function.



As noted in the design section. The code can be highly simplified by using a sentinel node. Also the loop to find the end should be removed by either using an end marker in the list or by using a doubly linked list.



Quick exit from delete.



If this returns nullptr then is there any reason to continue?



 Node *node = search(value);


Lot of extra work in delete.

I don't think you actually need tail.



Bug in print.



If the list is empty then your print is going to hit several places where it de-references a nullptr.



Destructor can be simplified.



You only need to loop over the list once to delete it. You don't need to unlink the tail from the head to make it work. Comparing pointers will still work after a pointer has been deleted. You just can't de-reference it after deleting.



template <class T>
circular_linked_list<T>::~circular_linked_list()

Node* last = nullptr;
Node* next
for(Node* loop = head; loop != last; loop = next)
last = head;
next = head->next;
delete loop;







share|improve this answer





















  • As long as the sentinel is not dynamically allocated, and does not necessitate constructing a T (who knows whether that can be default-constructed at all?), fine. The default-constructor being guaranteed to succeed is too convenient a property to give up.
    – Deduplicator
    Jan 30 at 0:27










  • As an example of a version with a sentinel: codereview.stackexchange.com/a/126007/507 It uses a automatic allocated sentinel node that does not have a data member (so avoiding the default allocator issues).
    – Martin York
    Jan 30 at 0:32










Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186239%2fcircular-linked-list-implementation-in-c%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
3
down vote



accepted










Design



With a singly linked list it is O(n) to find the end (assuming you store the beginning). So it is often nice to track both the beginning and end of the list inside the list object.



Personally I would have gone with a doubly linked list (next/prev). That way you can store just the head but finding the tail (on a circular list) is simply getting the link before the head.



// Head of a list
list->head

// Tail of a list (doubly linked)
list->head->prev;


Another thing you should consider is a sentinel link. This is a link in the list that does not contain data but is always there. Note: The list is empty when it only contains the sentinel element. The advantage to using a sentinel node is that you don't have to worry about nullptr when traversing the list. This drastically reduces the amount of code you need to write.



So from that long description two changes I would make.



  1. Doubly linked list next/prev

  2. Add a sentinel node.

Code Review



Move and Copy



I see you deleted the copy/move operations. Fine if you are still working through it. But in the end you will need to implement these. These are actually quite simple when you learn the tricks (idioms).



As a simple helpping hand:



circular_linked_list(const circular_linked_list& cll)
// This you need to implement in full.
// I leave the details to you (but a full deep copy of the list is needed).

// The assignment operator is easy.
// It is implemented in terms of the copy constructor.
// And the swap operation.
circular_linked_list& operator=(const circular_linked_list& cll)

circular_linked_list copy(cll); // Make a copy.
swap(copy); // Call the swap method.
return *this;
// Small improvement can be made (look up copy and swap idiom).



// Move is easily implemented using a swap.
circular_linked_list(circular_linked_list&& cll) noexcept // dont forget the noexcept
// Dont forget to initialize the members correctly.

// Swapping implements the requirements of move.
// It also provided deferred deletion (which is a nice side affect)
// allowing potential reuse of memory. But usually the object
// will be correctly and normally destroyed.
// It also provides the strong exception guarantee.
swap(cll);


circular_linked_list& operator=(circular_linked_list&& cll) noexcept

swap(cll);
return *this;


void swap(circular_linked_list& other) noexcept

using std::swap;
// Implement the swap of all members for other into this.


friend void swap(circular_linked_list& lhs, circular_linked_list& rhs)

lhs.swap(rhs);



Don't need an explicit constructor here.



 struct Node

T data;
Node * next;
Node(T value) : data(std::move(value)), next(nullptr)
;


If you use the brace initializer you don't need an explicit initializer as long as you use put the members in order.



 Node* node = new Node 12, nullptr ;


But you may want to explore some alternative constructors.



 // copy Cosntruct node.
Node(T const& value); // Pass by const reference and copy in
Node(T&& value); // Pass by r-value ref and move in
template<typename... P>
Node(P&&... args); // Pass using the arguments that constructor
// T allowing construction in place.


Printing



This is fine.



 void print_list();


But in C++ we usually print using the operator<<. So it is nice to define this operator for your class. Also std::cout is not the only stream you can print too (Files/Strings are two that pop out but lot of things are emulated like streams).



 void print_list(std::ostream& str = std::cout) const;
friend std::ostream& operator<<(std::ostream& str, circular_linked_list const& data)

data.print_list(str);
return str;



Perfectly good search.



 struct Node *search(T value)
{
Node *node = head;
while(node->next != head)

if(node->data == value)
return node;

node = node->next;



But this may have been easier to write as a for loop:



 for(Node *node = head; node->next != head; node = node->next)

if(node->data == value)
return node;




Insert



Both insert operations have a lot of shared code.
It would be better if you factor out the shared code into a separate function.



As noted in the design section. The code can be highly simplified by using a sentinel node. Also the loop to find the end should be removed by either using an end marker in the list or by using a doubly linked list.



Quick exit from delete.



If this returns nullptr then is there any reason to continue?



 Node *node = search(value);


Lot of extra work in delete.

I don't think you actually need tail.



Bug in print.



If the list is empty then your print is going to hit several places where it de-references a nullptr.



Destructor can be simplified.



You only need to loop over the list once to delete it. You don't need to unlink the tail from the head to make it work. Comparing pointers will still work after a pointer has been deleted. You just can't de-reference it after deleting.



template <class T>
circular_linked_list<T>::~circular_linked_list()

Node* last = nullptr;
Node* next
for(Node* loop = head; loop != last; loop = next)
last = head;
next = head->next;
delete loop;







share|improve this answer





















  • As long as the sentinel is not dynamically allocated, and does not necessitate constructing a T (who knows whether that can be default-constructed at all?), fine. The default-constructor being guaranteed to succeed is too convenient a property to give up.
    – Deduplicator
    Jan 30 at 0:27










  • As an example of a version with a sentinel: codereview.stackexchange.com/a/126007/507 It uses a automatic allocated sentinel node that does not have a data member (so avoiding the default allocator issues).
    – Martin York
    Jan 30 at 0:32














up vote
3
down vote



accepted










Design



With a singly linked list it is O(n) to find the end (assuming you store the beginning). So it is often nice to track both the beginning and end of the list inside the list object.



Personally I would have gone with a doubly linked list (next/prev). That way you can store just the head but finding the tail (on a circular list) is simply getting the link before the head.



// Head of a list
list->head

// Tail of a list (doubly linked)
list->head->prev;


Another thing you should consider is a sentinel link. This is a link in the list that does not contain data but is always there. Note: The list is empty when it only contains the sentinel element. The advantage to using a sentinel node is that you don't have to worry about nullptr when traversing the list. This drastically reduces the amount of code you need to write.



So from that long description two changes I would make.



  1. Doubly linked list next/prev

  2. Add a sentinel node.

Code Review



Move and Copy



I see you deleted the copy/move operations. Fine if you are still working through it. But in the end you will need to implement these. These are actually quite simple when you learn the tricks (idioms).



As a simple helpping hand:



circular_linked_list(const circular_linked_list& cll)
// This you need to implement in full.
// I leave the details to you (but a full deep copy of the list is needed).

// The assignment operator is easy.
// It is implemented in terms of the copy constructor.
// And the swap operation.
circular_linked_list& operator=(const circular_linked_list& cll)

circular_linked_list copy(cll); // Make a copy.
swap(copy); // Call the swap method.
return *this;
// Small improvement can be made (look up copy and swap idiom).



// Move is easily implemented using a swap.
circular_linked_list(circular_linked_list&& cll) noexcept // dont forget the noexcept
// Dont forget to initialize the members correctly.

// Swapping implements the requirements of move.
// It also provided deferred deletion (which is a nice side affect)
// allowing potential reuse of memory. But usually the object
// will be correctly and normally destroyed.
// It also provides the strong exception guarantee.
swap(cll);


circular_linked_list& operator=(circular_linked_list&& cll) noexcept

swap(cll);
return *this;


void swap(circular_linked_list& other) noexcept

using std::swap;
// Implement the swap of all members for other into this.


friend void swap(circular_linked_list& lhs, circular_linked_list& rhs)

lhs.swap(rhs);



Don't need an explicit constructor here.



 struct Node

T data;
Node * next;
Node(T value) : data(std::move(value)), next(nullptr)
;


If you use the brace initializer you don't need an explicit initializer as long as you use put the members in order.



 Node* node = new Node 12, nullptr ;


But you may want to explore some alternative constructors.



 // copy Cosntruct node.
Node(T const& value); // Pass by const reference and copy in
Node(T&& value); // Pass by r-value ref and move in
template<typename... P>
Node(P&&... args); // Pass using the arguments that constructor
// T allowing construction in place.


Printing



This is fine.



 void print_list();


But in C++ we usually print using the operator<<. So it is nice to define this operator for your class. Also std::cout is not the only stream you can print too (Files/Strings are two that pop out but lot of things are emulated like streams).



 void print_list(std::ostream& str = std::cout) const;
friend std::ostream& operator<<(std::ostream& str, circular_linked_list const& data)

data.print_list(str);
return str;



Perfectly good search.



 struct Node *search(T value)
{
Node *node = head;
while(node->next != head)

if(node->data == value)
return node;

node = node->next;



But this may have been easier to write as a for loop:



 for(Node *node = head; node->next != head; node = node->next)

if(node->data == value)
return node;




Insert



Both insert operations have a lot of shared code.
It would be better if you factor out the shared code into a separate function.



As noted in the design section. The code can be highly simplified by using a sentinel node. Also the loop to find the end should be removed by either using an end marker in the list or by using a doubly linked list.



Quick exit from delete.



If this returns nullptr then is there any reason to continue?



 Node *node = search(value);


Lot of extra work in delete.

I don't think you actually need tail.



Bug in print.



If the list is empty then your print is going to hit several places where it de-references a nullptr.



Destructor can be simplified.



You only need to loop over the list once to delete it. You don't need to unlink the tail from the head to make it work. Comparing pointers will still work after a pointer has been deleted. You just can't de-reference it after deleting.



template <class T>
circular_linked_list<T>::~circular_linked_list()

Node* last = nullptr;
Node* next
for(Node* loop = head; loop != last; loop = next)
last = head;
next = head->next;
delete loop;







share|improve this answer





















  • As long as the sentinel is not dynamically allocated, and does not necessitate constructing a T (who knows whether that can be default-constructed at all?), fine. The default-constructor being guaranteed to succeed is too convenient a property to give up.
    – Deduplicator
    Jan 30 at 0:27










  • As an example of a version with a sentinel: codereview.stackexchange.com/a/126007/507 It uses a automatic allocated sentinel node that does not have a data member (so avoiding the default allocator issues).
    – Martin York
    Jan 30 at 0:32












up vote
3
down vote



accepted







up vote
3
down vote



accepted






Design



With a singly linked list it is O(n) to find the end (assuming you store the beginning). So it is often nice to track both the beginning and end of the list inside the list object.



Personally I would have gone with a doubly linked list (next/prev). That way you can store just the head but finding the tail (on a circular list) is simply getting the link before the head.



// Head of a list
list->head

// Tail of a list (doubly linked)
list->head->prev;


Another thing you should consider is a sentinel link. This is a link in the list that does not contain data but is always there. Note: The list is empty when it only contains the sentinel element. The advantage to using a sentinel node is that you don't have to worry about nullptr when traversing the list. This drastically reduces the amount of code you need to write.



So from that long description two changes I would make.



  1. Doubly linked list next/prev

  2. Add a sentinel node.

Code Review



Move and Copy



I see you deleted the copy/move operations. Fine if you are still working through it. But in the end you will need to implement these. These are actually quite simple when you learn the tricks (idioms).



As a simple helpping hand:



circular_linked_list(const circular_linked_list& cll)
// This you need to implement in full.
// I leave the details to you (but a full deep copy of the list is needed).

// The assignment operator is easy.
// It is implemented in terms of the copy constructor.
// And the swap operation.
circular_linked_list& operator=(const circular_linked_list& cll)

circular_linked_list copy(cll); // Make a copy.
swap(copy); // Call the swap method.
return *this;
// Small improvement can be made (look up copy and swap idiom).



// Move is easily implemented using a swap.
circular_linked_list(circular_linked_list&& cll) noexcept // dont forget the noexcept
// Dont forget to initialize the members correctly.

// Swapping implements the requirements of move.
// It also provided deferred deletion (which is a nice side affect)
// allowing potential reuse of memory. But usually the object
// will be correctly and normally destroyed.
// It also provides the strong exception guarantee.
swap(cll);


circular_linked_list& operator=(circular_linked_list&& cll) noexcept

swap(cll);
return *this;


void swap(circular_linked_list& other) noexcept

using std::swap;
// Implement the swap of all members for other into this.


friend void swap(circular_linked_list& lhs, circular_linked_list& rhs)

lhs.swap(rhs);



Don't need an explicit constructor here.



 struct Node

T data;
Node * next;
Node(T value) : data(std::move(value)), next(nullptr)
;


If you use the brace initializer you don't need an explicit initializer as long as you use put the members in order.



 Node* node = new Node 12, nullptr ;


But you may want to explore some alternative constructors.



 // copy Cosntruct node.
Node(T const& value); // Pass by const reference and copy in
Node(T&& value); // Pass by r-value ref and move in
template<typename... P>
Node(P&&... args); // Pass using the arguments that constructor
// T allowing construction in place.


Printing



This is fine.



 void print_list();


But in C++ we usually print using the operator<<. So it is nice to define this operator for your class. Also std::cout is not the only stream you can print too (Files/Strings are two that pop out but lot of things are emulated like streams).



 void print_list(std::ostream& str = std::cout) const;
friend std::ostream& operator<<(std::ostream& str, circular_linked_list const& data)

data.print_list(str);
return str;



Perfectly good search.



 struct Node *search(T value)
{
Node *node = head;
while(node->next != head)

if(node->data == value)
return node;

node = node->next;



But this may have been easier to write as a for loop:



 for(Node *node = head; node->next != head; node = node->next)

if(node->data == value)
return node;




Insert



Both insert operations have a lot of shared code.
It would be better if you factor out the shared code into a separate function.



As noted in the design section. The code can be highly simplified by using a sentinel node. Also the loop to find the end should be removed by either using an end marker in the list or by using a doubly linked list.



Quick exit from delete.



If this returns nullptr then is there any reason to continue?



 Node *node = search(value);


Lot of extra work in delete.

I don't think you actually need tail.



Bug in print.



If the list is empty then your print is going to hit several places where it de-references a nullptr.



Destructor can be simplified.



You only need to loop over the list once to delete it. You don't need to unlink the tail from the head to make it work. Comparing pointers will still work after a pointer has been deleted. You just can't de-reference it after deleting.



template <class T>
circular_linked_list<T>::~circular_linked_list()

Node* last = nullptr;
Node* next
for(Node* loop = head; loop != last; loop = next)
last = head;
next = head->next;
delete loop;







share|improve this answer













Design



With a singly linked list it is O(n) to find the end (assuming you store the beginning). So it is often nice to track both the beginning and end of the list inside the list object.



Personally I would have gone with a doubly linked list (next/prev). That way you can store just the head but finding the tail (on a circular list) is simply getting the link before the head.



// Head of a list
list->head

// Tail of a list (doubly linked)
list->head->prev;


Another thing you should consider is a sentinel link. This is a link in the list that does not contain data but is always there. Note: The list is empty when it only contains the sentinel element. The advantage to using a sentinel node is that you don't have to worry about nullptr when traversing the list. This drastically reduces the amount of code you need to write.



So from that long description two changes I would make.



  1. Doubly linked list next/prev

  2. Add a sentinel node.

Code Review



Move and Copy



I see you deleted the copy/move operations. Fine if you are still working through it. But in the end you will need to implement these. These are actually quite simple when you learn the tricks (idioms).



As a simple helpping hand:



circular_linked_list(const circular_linked_list& cll)
// This you need to implement in full.
// I leave the details to you (but a full deep copy of the list is needed).

// The assignment operator is easy.
// It is implemented in terms of the copy constructor.
// And the swap operation.
circular_linked_list& operator=(const circular_linked_list& cll)

circular_linked_list copy(cll); // Make a copy.
swap(copy); // Call the swap method.
return *this;
// Small improvement can be made (look up copy and swap idiom).



// Move is easily implemented using a swap.
circular_linked_list(circular_linked_list&& cll) noexcept // dont forget the noexcept
// Dont forget to initialize the members correctly.

// Swapping implements the requirements of move.
// It also provided deferred deletion (which is a nice side affect)
// allowing potential reuse of memory. But usually the object
// will be correctly and normally destroyed.
// It also provides the strong exception guarantee.
swap(cll);


circular_linked_list& operator=(circular_linked_list&& cll) noexcept

swap(cll);
return *this;


void swap(circular_linked_list& other) noexcept

using std::swap;
// Implement the swap of all members for other into this.


friend void swap(circular_linked_list& lhs, circular_linked_list& rhs)

lhs.swap(rhs);



Don't need an explicit constructor here.



 struct Node

T data;
Node * next;
Node(T value) : data(std::move(value)), next(nullptr)
;


If you use the brace initializer you don't need an explicit initializer as long as you use put the members in order.



 Node* node = new Node 12, nullptr ;


But you may want to explore some alternative constructors.



 // copy Cosntruct node.
Node(T const& value); // Pass by const reference and copy in
Node(T&& value); // Pass by r-value ref and move in
template<typename... P>
Node(P&&... args); // Pass using the arguments that constructor
// T allowing construction in place.


Printing



This is fine.



 void print_list();


But in C++ we usually print using the operator<<. So it is nice to define this operator for your class. Also std::cout is not the only stream you can print too (Files/Strings are two that pop out but lot of things are emulated like streams).



 void print_list(std::ostream& str = std::cout) const;
friend std::ostream& operator<<(std::ostream& str, circular_linked_list const& data)

data.print_list(str);
return str;



Perfectly good search.



 struct Node *search(T value)
{
Node *node = head;
while(node->next != head)

if(node->data == value)
return node;

node = node->next;



But this may have been easier to write as a for loop:



 for(Node *node = head; node->next != head; node = node->next)

if(node->data == value)
return node;




Insert



Both insert operations have a lot of shared code.
It would be better if you factor out the shared code into a separate function.



As noted in the design section. The code can be highly simplified by using a sentinel node. Also the loop to find the end should be removed by either using an end marker in the list or by using a doubly linked list.



Quick exit from delete.



If this returns nullptr then is there any reason to continue?



 Node *node = search(value);


Lot of extra work in delete.

I don't think you actually need tail.



Bug in print.



If the list is empty then your print is going to hit several places where it de-references a nullptr.



Destructor can be simplified.



You only need to loop over the list once to delete it. You don't need to unlink the tail from the head to make it work. Comparing pointers will still work after a pointer has been deleted. You just can't de-reference it after deleting.



template <class T>
circular_linked_list<T>::~circular_linked_list()

Node* last = nullptr;
Node* next
for(Node* loop = head; loop != last; loop = next)
last = head;
next = head->next;
delete loop;








share|improve this answer













share|improve this answer



share|improve this answer











answered Jan 29 at 18:36









Martin York

70.9k481244




70.9k481244











  • As long as the sentinel is not dynamically allocated, and does not necessitate constructing a T (who knows whether that can be default-constructed at all?), fine. The default-constructor being guaranteed to succeed is too convenient a property to give up.
    – Deduplicator
    Jan 30 at 0:27










  • As an example of a version with a sentinel: codereview.stackexchange.com/a/126007/507 It uses a automatic allocated sentinel node that does not have a data member (so avoiding the default allocator issues).
    – Martin York
    Jan 30 at 0:32
















  • As long as the sentinel is not dynamically allocated, and does not necessitate constructing a T (who knows whether that can be default-constructed at all?), fine. The default-constructor being guaranteed to succeed is too convenient a property to give up.
    – Deduplicator
    Jan 30 at 0:27










  • As an example of a version with a sentinel: codereview.stackexchange.com/a/126007/507 It uses a automatic allocated sentinel node that does not have a data member (so avoiding the default allocator issues).
    – Martin York
    Jan 30 at 0:32















As long as the sentinel is not dynamically allocated, and does not necessitate constructing a T (who knows whether that can be default-constructed at all?), fine. The default-constructor being guaranteed to succeed is too convenient a property to give up.
– Deduplicator
Jan 30 at 0:27




As long as the sentinel is not dynamically allocated, and does not necessitate constructing a T (who knows whether that can be default-constructed at all?), fine. The default-constructor being guaranteed to succeed is too convenient a property to give up.
– Deduplicator
Jan 30 at 0:27












As an example of a version with a sentinel: codereview.stackexchange.com/a/126007/507 It uses a automatic allocated sentinel node that does not have a data member (so avoiding the default allocator issues).
– Martin York
Jan 30 at 0:32




As an example of a version with a sentinel: codereview.stackexchange.com/a/126007/507 It uses a automatic allocated sentinel node that does not have a data member (so avoiding the default allocator issues).
– Martin York
Jan 30 at 0:32












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f186239%2fcircular-linked-list-implementation-in-c%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Python Lists

Aion

JavaScript Array Iteration Methods