RAII-style single-linked list

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
6
down vote

favorite
1












After watching Herb Sutter describe single-linked lists in terms of unique_ptr I decided to implement my own. In particular I want to know if my move semantics are correct and if any unnecessary copies are made. It passes my simple cases with int and I plan to add insertion/deleting later.



#include <iostream>
#include <memory>
#include <cstddef>
#include <utility>

template<typename Type>
class Node
public:
Node(const Type& data) : data data
std::unique_ptr<Node<Type>> next = nullptr;
Type data;
;

template<typename Type>
class LinkedList
public:
void push_back(const Type& data)
if (!head)
head = std::make_unique<Node<Type>>(data);
return;


Node<Type>* end = head.get();

while (end->next)
end = end->next.get();


end->next = std::make_unique<Node<Type>>(data);


Type pop_back()
if (!head)
throw;


Node<Type>* end = head.get();
Node<Type>* previous = nullptr;

while (end->next)
previous = end;
end = end->next.get();


const Type data = end->data;

if (previous)
previous->next = nullptr;


return data;


void push_front(const Type& data)
if (!head)
head = std::make_unique<Node<Type>>(data);
return;


std::unique_ptr<Node<Type>> new_head = std::make_unique<Node<Type>>(data);
new_head->next = std::move(head);
head = std::move(new_head);


Type pop_front()
if (!head)
throw;


const Type data = head->data;
head = std::move(head->next);
return data;


std::size_t size() const
if (!head)
return 0;


std::size_t size = 1;
Node<Type>* traverse = head.get();

while (traverse->next)
traverse = traverse->next.get();
size++;


return size;


/* iterative destroy to avoid recursive deletes */
void destroy()
if (!head)
return;


while (head->next)
head = std::move(head->next);


head = nullptr;


private:
std::unique_ptr<Node<Type>> head = nullptr;
;


Edit: With Deduplicator's response I ended up with this code



#include <iostream>
#include <memory>
#include <cstddef>
#include <utility>

template<typename Type>
class Node
public:
Node(const Type& data) : data data
std::unique_ptr<Node<Type>> next = nullptr;
Type data;
;

template<typename Type>
class LinkedList
public:
void push_back(const Type& data)
auto end = &head;

while (*end)
end = &(*end)->next;


*end = std::make_unique<Node<Type>>(data);


Type pop_back()
auto end = &head;
auto previous = &head;

while (*end)
previous = end;
end = &(*end)->next;


const Type data = (*previous)->data;
*previous = nullptr;
return data;


void push_front(const Type& data)
auto new_head = std::make_unique<Node<Type>>(data);
new_head->next = std::move(head);
head = std::move(new_head);


Type pop_front()
const Type data = head->data;
head = std::move(head->next);
return data;


~LinkedList()
auto end = &head;

while (*end)
*end = std::move((*end)->next);



private:
std::unique_ptr<Node<Type>> head = nullptr;
;






share|improve this question





















  • You have a tendency to regard the empty list as a special case. Try for more uniformity!
    – Deduplicator
    Feb 24 at 19:24










  • What do you mean?
    – Brady Dean
    Feb 25 at 13:46
















up vote
6
down vote

favorite
1












After watching Herb Sutter describe single-linked lists in terms of unique_ptr I decided to implement my own. In particular I want to know if my move semantics are correct and if any unnecessary copies are made. It passes my simple cases with int and I plan to add insertion/deleting later.



#include <iostream>
#include <memory>
#include <cstddef>
#include <utility>

template<typename Type>
class Node
public:
Node(const Type& data) : data data
std::unique_ptr<Node<Type>> next = nullptr;
Type data;
;

template<typename Type>
class LinkedList
public:
void push_back(const Type& data)
if (!head)
head = std::make_unique<Node<Type>>(data);
return;


Node<Type>* end = head.get();

while (end->next)
end = end->next.get();


end->next = std::make_unique<Node<Type>>(data);


Type pop_back()
if (!head)
throw;


Node<Type>* end = head.get();
Node<Type>* previous = nullptr;

while (end->next)
previous = end;
end = end->next.get();


const Type data = end->data;

if (previous)
previous->next = nullptr;


return data;


void push_front(const Type& data)
if (!head)
head = std::make_unique<Node<Type>>(data);
return;


std::unique_ptr<Node<Type>> new_head = std::make_unique<Node<Type>>(data);
new_head->next = std::move(head);
head = std::move(new_head);


Type pop_front()
if (!head)
throw;


const Type data = head->data;
head = std::move(head->next);
return data;


std::size_t size() const
if (!head)
return 0;


std::size_t size = 1;
Node<Type>* traverse = head.get();

while (traverse->next)
traverse = traverse->next.get();
size++;


return size;


/* iterative destroy to avoid recursive deletes */
void destroy()
if (!head)
return;


while (head->next)
head = std::move(head->next);


head = nullptr;


private:
std::unique_ptr<Node<Type>> head = nullptr;
;


Edit: With Deduplicator's response I ended up with this code



#include <iostream>
#include <memory>
#include <cstddef>
#include <utility>

template<typename Type>
class Node
public:
Node(const Type& data) : data data
std::unique_ptr<Node<Type>> next = nullptr;
Type data;
;

template<typename Type>
class LinkedList
public:
void push_back(const Type& data)
auto end = &head;

while (*end)
end = &(*end)->next;


*end = std::make_unique<Node<Type>>(data);


Type pop_back()
auto end = &head;
auto previous = &head;

while (*end)
previous = end;
end = &(*end)->next;


const Type data = (*previous)->data;
*previous = nullptr;
return data;


void push_front(const Type& data)
auto new_head = std::make_unique<Node<Type>>(data);
new_head->next = std::move(head);
head = std::move(new_head);


Type pop_front()
const Type data = head->data;
head = std::move(head->next);
return data;


~LinkedList()
auto end = &head;

while (*end)
*end = std::move((*end)->next);



private:
std::unique_ptr<Node<Type>> head = nullptr;
;






share|improve this question





















  • You have a tendency to regard the empty list as a special case. Try for more uniformity!
    – Deduplicator
    Feb 24 at 19:24










  • What do you mean?
    – Brady Dean
    Feb 25 at 13:46












up vote
6
down vote

favorite
1









up vote
6
down vote

favorite
1






1





After watching Herb Sutter describe single-linked lists in terms of unique_ptr I decided to implement my own. In particular I want to know if my move semantics are correct and if any unnecessary copies are made. It passes my simple cases with int and I plan to add insertion/deleting later.



#include <iostream>
#include <memory>
#include <cstddef>
#include <utility>

template<typename Type>
class Node
public:
Node(const Type& data) : data data
std::unique_ptr<Node<Type>> next = nullptr;
Type data;
;

template<typename Type>
class LinkedList
public:
void push_back(const Type& data)
if (!head)
head = std::make_unique<Node<Type>>(data);
return;


Node<Type>* end = head.get();

while (end->next)
end = end->next.get();


end->next = std::make_unique<Node<Type>>(data);


Type pop_back()
if (!head)
throw;


Node<Type>* end = head.get();
Node<Type>* previous = nullptr;

while (end->next)
previous = end;
end = end->next.get();


const Type data = end->data;

if (previous)
previous->next = nullptr;


return data;


void push_front(const Type& data)
if (!head)
head = std::make_unique<Node<Type>>(data);
return;


std::unique_ptr<Node<Type>> new_head = std::make_unique<Node<Type>>(data);
new_head->next = std::move(head);
head = std::move(new_head);


Type pop_front()
if (!head)
throw;


const Type data = head->data;
head = std::move(head->next);
return data;


std::size_t size() const
if (!head)
return 0;


std::size_t size = 1;
Node<Type>* traverse = head.get();

while (traverse->next)
traverse = traverse->next.get();
size++;


return size;


/* iterative destroy to avoid recursive deletes */
void destroy()
if (!head)
return;


while (head->next)
head = std::move(head->next);


head = nullptr;


private:
std::unique_ptr<Node<Type>> head = nullptr;
;


Edit: With Deduplicator's response I ended up with this code



#include <iostream>
#include <memory>
#include <cstddef>
#include <utility>

template<typename Type>
class Node
public:
Node(const Type& data) : data data
std::unique_ptr<Node<Type>> next = nullptr;
Type data;
;

template<typename Type>
class LinkedList
public:
void push_back(const Type& data)
auto end = &head;

while (*end)
end = &(*end)->next;


*end = std::make_unique<Node<Type>>(data);


Type pop_back()
auto end = &head;
auto previous = &head;

while (*end)
previous = end;
end = &(*end)->next;


const Type data = (*previous)->data;
*previous = nullptr;
return data;


void push_front(const Type& data)
auto new_head = std::make_unique<Node<Type>>(data);
new_head->next = std::move(head);
head = std::move(new_head);


Type pop_front()
const Type data = head->data;
head = std::move(head->next);
return data;


~LinkedList()
auto end = &head;

while (*end)
*end = std::move((*end)->next);



private:
std::unique_ptr<Node<Type>> head = nullptr;
;






share|improve this question













After watching Herb Sutter describe single-linked lists in terms of unique_ptr I decided to implement my own. In particular I want to know if my move semantics are correct and if any unnecessary copies are made. It passes my simple cases with int and I plan to add insertion/deleting later.



#include <iostream>
#include <memory>
#include <cstddef>
#include <utility>

template<typename Type>
class Node
public:
Node(const Type& data) : data data
std::unique_ptr<Node<Type>> next = nullptr;
Type data;
;

template<typename Type>
class LinkedList
public:
void push_back(const Type& data)
if (!head)
head = std::make_unique<Node<Type>>(data);
return;


Node<Type>* end = head.get();

while (end->next)
end = end->next.get();


end->next = std::make_unique<Node<Type>>(data);


Type pop_back()
if (!head)
throw;


Node<Type>* end = head.get();
Node<Type>* previous = nullptr;

while (end->next)
previous = end;
end = end->next.get();


const Type data = end->data;

if (previous)
previous->next = nullptr;


return data;


void push_front(const Type& data)
if (!head)
head = std::make_unique<Node<Type>>(data);
return;


std::unique_ptr<Node<Type>> new_head = std::make_unique<Node<Type>>(data);
new_head->next = std::move(head);
head = std::move(new_head);


Type pop_front()
if (!head)
throw;


const Type data = head->data;
head = std::move(head->next);
return data;


std::size_t size() const
if (!head)
return 0;


std::size_t size = 1;
Node<Type>* traverse = head.get();

while (traverse->next)
traverse = traverse->next.get();
size++;


return size;


/* iterative destroy to avoid recursive deletes */
void destroy()
if (!head)
return;


while (head->next)
head = std::move(head->next);


head = nullptr;


private:
std::unique_ptr<Node<Type>> head = nullptr;
;


Edit: With Deduplicator's response I ended up with this code



#include <iostream>
#include <memory>
#include <cstddef>
#include <utility>

template<typename Type>
class Node
public:
Node(const Type& data) : data data
std::unique_ptr<Node<Type>> next = nullptr;
Type data;
;

template<typename Type>
class LinkedList
public:
void push_back(const Type& data)
auto end = &head;

while (*end)
end = &(*end)->next;


*end = std::make_unique<Node<Type>>(data);


Type pop_back()
auto end = &head;
auto previous = &head;

while (*end)
previous = end;
end = &(*end)->next;


const Type data = (*previous)->data;
*previous = nullptr;
return data;


void push_front(const Type& data)
auto new_head = std::make_unique<Node<Type>>(data);
new_head->next = std::move(head);
head = std::move(new_head);


Type pop_front()
const Type data = head->data;
head = std::move(head->next);
return data;


~LinkedList()
auto end = &head;

while (*end)
*end = std::move((*end)->next);



private:
std::unique_ptr<Node<Type>> head = nullptr;
;








share|improve this question












share|improve this question




share|improve this question








edited Feb 26 at 3:04
























asked Feb 24 at 5:11









Brady Dean

21616




21616











  • You have a tendency to regard the empty list as a special case. Try for more uniformity!
    – Deduplicator
    Feb 24 at 19:24










  • What do you mean?
    – Brady Dean
    Feb 25 at 13:46
















  • You have a tendency to regard the empty list as a special case. Try for more uniformity!
    – Deduplicator
    Feb 24 at 19:24










  • What do you mean?
    – Brady Dean
    Feb 25 at 13:46















You have a tendency to regard the empty list as a special case. Try for more uniformity!
– Deduplicator
Feb 24 at 19:24




You have a tendency to regard the empty list as a special case. Try for more uniformity!
– Deduplicator
Feb 24 at 19:24












What do you mean?
– Brady Dean
Feb 25 at 13:46




What do you mean?
– Brady Dean
Feb 25 at 13:46










2 Answers
2






active

oldest

votes

















up vote
3
down vote



accepted










Almost always auto. Only write down the specific type when you have to, it's shorter, clearer, and less error-prone.



You currently handle an empty list as a special case. Don't, it's neither efficient nor elegant.

Instead, use a pointer to pointer, and everything is simplified. As an example:



void push_back(const Type& data) 
auto p = &head;
while (*p)
p = &(*p)->next;
*p = std::make_unique<Node<Type>>(data);



Good of you to avoid recursive descent in the dtor. But, you know that's not spelled destroy()?






share|improve this answer





















  • Thanks for showing me that. I updated my functions with that pattern
    – Brady Dean
    Feb 26 at 2:54










  • The alternative is to maintain a "dummy head" that never has data, but just serves to point to the first real node (when there is one). Works well with doubly-linked lists, too.
    – Toby Speight
    Feb 26 at 17:22










  • auto is good, but shouldn't be over-used. I have seen it used for function arguments, that scared the hell out of me (turns out it's a proposed feature that turns the function into a template).
    – Cris Luengo
    Feb 27 at 3:18

















up vote
2
down vote













While this is somewhat unrelated to what you want from this review, here is something I don't like:



std::size_t size() const 
if (!head)
return 0;


std::size_t size = 1;
Node<Type>* traverse = head.get();

while (traverse->next)
traverse = traverse->next.get();
size++;


return size;



This function is currently very complex, in terms of readability and performance. If the client had a loop that repeatedly needed the size of the linked list after modifying it, this would slow the code down. Instead, I suggest that you keep a private member variable called size in your class. Every time you add an element to your list, you increment this variable. Similarly, you decrement this variable when you delete something. Thus this function would then become O(1):



std::size_t size() const 
return size;



This matches the behavior of the STL. However, adding such a member could bloat your class, as @Frank pointed out. If you want to really follow the STL, then I would remove the size function altogether, as std::forward_list does for these reasons.






share|improve this answer























  • That makes sense. Thanks
    – Brady Dean
    Feb 24 at 17:20






  • 2




    I disagree. If you are regularly asking the size of a linked list, then you are doing something very wrong in the first place. So bloating the sizeof() and making the class invariant more complex just to accommodate what should be a code smell is pretty iffy on my book.
    – Frank
    Feb 24 at 18:33











  • @Frank I mean, most STL containers have a size() function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception is std::forward_list, but that removes the size function itself for the reason you said. The choice is between keeping a size function in constant time, or removing the function itself. Since OP is keeping it, I recommend changing it to perform in constant time.
    – Arnav Borborah
    Feb 24 at 18:41











  • To illustrate my point, OP's move semantics are currently correct, but adding a size member would require adding a move constructor and = operator.
    – Frank
    Feb 24 at 18:41






  • 3




    @Rakete1111 when moving the list, you need to reset the size of the original list to 0 since it will be empty after the move.
    – Frank
    Feb 25 at 18:25










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%2f188246%2fraii-style-single-linked-list%23new-answer', 'question_page');

);

Post as a guest






























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
3
down vote



accepted










Almost always auto. Only write down the specific type when you have to, it's shorter, clearer, and less error-prone.



You currently handle an empty list as a special case. Don't, it's neither efficient nor elegant.

Instead, use a pointer to pointer, and everything is simplified. As an example:



void push_back(const Type& data) 
auto p = &head;
while (*p)
p = &(*p)->next;
*p = std::make_unique<Node<Type>>(data);



Good of you to avoid recursive descent in the dtor. But, you know that's not spelled destroy()?






share|improve this answer





















  • Thanks for showing me that. I updated my functions with that pattern
    – Brady Dean
    Feb 26 at 2:54










  • The alternative is to maintain a "dummy head" that never has data, but just serves to point to the first real node (when there is one). Works well with doubly-linked lists, too.
    – Toby Speight
    Feb 26 at 17:22










  • auto is good, but shouldn't be over-used. I have seen it used for function arguments, that scared the hell out of me (turns out it's a proposed feature that turns the function into a template).
    – Cris Luengo
    Feb 27 at 3:18














up vote
3
down vote



accepted










Almost always auto. Only write down the specific type when you have to, it's shorter, clearer, and less error-prone.



You currently handle an empty list as a special case. Don't, it's neither efficient nor elegant.

Instead, use a pointer to pointer, and everything is simplified. As an example:



void push_back(const Type& data) 
auto p = &head;
while (*p)
p = &(*p)->next;
*p = std::make_unique<Node<Type>>(data);



Good of you to avoid recursive descent in the dtor. But, you know that's not spelled destroy()?






share|improve this answer





















  • Thanks for showing me that. I updated my functions with that pattern
    – Brady Dean
    Feb 26 at 2:54










  • The alternative is to maintain a "dummy head" that never has data, but just serves to point to the first real node (when there is one). Works well with doubly-linked lists, too.
    – Toby Speight
    Feb 26 at 17:22










  • auto is good, but shouldn't be over-used. I have seen it used for function arguments, that scared the hell out of me (turns out it's a proposed feature that turns the function into a template).
    – Cris Luengo
    Feb 27 at 3:18












up vote
3
down vote



accepted







up vote
3
down vote



accepted






Almost always auto. Only write down the specific type when you have to, it's shorter, clearer, and less error-prone.



You currently handle an empty list as a special case. Don't, it's neither efficient nor elegant.

Instead, use a pointer to pointer, and everything is simplified. As an example:



void push_back(const Type& data) 
auto p = &head;
while (*p)
p = &(*p)->next;
*p = std::make_unique<Node<Type>>(data);



Good of you to avoid recursive descent in the dtor. But, you know that's not spelled destroy()?






share|improve this answer













Almost always auto. Only write down the specific type when you have to, it's shorter, clearer, and less error-prone.



You currently handle an empty list as a special case. Don't, it's neither efficient nor elegant.

Instead, use a pointer to pointer, and everything is simplified. As an example:



void push_back(const Type& data) 
auto p = &head;
while (*p)
p = &(*p)->next;
*p = std::make_unique<Node<Type>>(data);



Good of you to avoid recursive descent in the dtor. But, you know that's not spelled destroy()?







share|improve this answer













share|improve this answer



share|improve this answer











answered Feb 25 at 21:19









Deduplicator

9,8721844




9,8721844











  • Thanks for showing me that. I updated my functions with that pattern
    – Brady Dean
    Feb 26 at 2:54










  • The alternative is to maintain a "dummy head" that never has data, but just serves to point to the first real node (when there is one). Works well with doubly-linked lists, too.
    – Toby Speight
    Feb 26 at 17:22










  • auto is good, but shouldn't be over-used. I have seen it used for function arguments, that scared the hell out of me (turns out it's a proposed feature that turns the function into a template).
    – Cris Luengo
    Feb 27 at 3:18
















  • Thanks for showing me that. I updated my functions with that pattern
    – Brady Dean
    Feb 26 at 2:54










  • The alternative is to maintain a "dummy head" that never has data, but just serves to point to the first real node (when there is one). Works well with doubly-linked lists, too.
    – Toby Speight
    Feb 26 at 17:22










  • auto is good, but shouldn't be over-used. I have seen it used for function arguments, that scared the hell out of me (turns out it's a proposed feature that turns the function into a template).
    – Cris Luengo
    Feb 27 at 3:18















Thanks for showing me that. I updated my functions with that pattern
– Brady Dean
Feb 26 at 2:54




Thanks for showing me that. I updated my functions with that pattern
– Brady Dean
Feb 26 at 2:54












The alternative is to maintain a "dummy head" that never has data, but just serves to point to the first real node (when there is one). Works well with doubly-linked lists, too.
– Toby Speight
Feb 26 at 17:22




The alternative is to maintain a "dummy head" that never has data, but just serves to point to the first real node (when there is one). Works well with doubly-linked lists, too.
– Toby Speight
Feb 26 at 17:22












auto is good, but shouldn't be over-used. I have seen it used for function arguments, that scared the hell out of me (turns out it's a proposed feature that turns the function into a template).
– Cris Luengo
Feb 27 at 3:18




auto is good, but shouldn't be over-used. I have seen it used for function arguments, that scared the hell out of me (turns out it's a proposed feature that turns the function into a template).
– Cris Luengo
Feb 27 at 3:18












up vote
2
down vote













While this is somewhat unrelated to what you want from this review, here is something I don't like:



std::size_t size() const 
if (!head)
return 0;


std::size_t size = 1;
Node<Type>* traverse = head.get();

while (traverse->next)
traverse = traverse->next.get();
size++;


return size;



This function is currently very complex, in terms of readability and performance. If the client had a loop that repeatedly needed the size of the linked list after modifying it, this would slow the code down. Instead, I suggest that you keep a private member variable called size in your class. Every time you add an element to your list, you increment this variable. Similarly, you decrement this variable when you delete something. Thus this function would then become O(1):



std::size_t size() const 
return size;



This matches the behavior of the STL. However, adding such a member could bloat your class, as @Frank pointed out. If you want to really follow the STL, then I would remove the size function altogether, as std::forward_list does for these reasons.






share|improve this answer























  • That makes sense. Thanks
    – Brady Dean
    Feb 24 at 17:20






  • 2




    I disagree. If you are regularly asking the size of a linked list, then you are doing something very wrong in the first place. So bloating the sizeof() and making the class invariant more complex just to accommodate what should be a code smell is pretty iffy on my book.
    – Frank
    Feb 24 at 18:33











  • @Frank I mean, most STL containers have a size() function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception is std::forward_list, but that removes the size function itself for the reason you said. The choice is between keeping a size function in constant time, or removing the function itself. Since OP is keeping it, I recommend changing it to perform in constant time.
    – Arnav Borborah
    Feb 24 at 18:41











  • To illustrate my point, OP's move semantics are currently correct, but adding a size member would require adding a move constructor and = operator.
    – Frank
    Feb 24 at 18:41






  • 3




    @Rakete1111 when moving the list, you need to reset the size of the original list to 0 since it will be empty after the move.
    – Frank
    Feb 25 at 18:25














up vote
2
down vote













While this is somewhat unrelated to what you want from this review, here is something I don't like:



std::size_t size() const 
if (!head)
return 0;


std::size_t size = 1;
Node<Type>* traverse = head.get();

while (traverse->next)
traverse = traverse->next.get();
size++;


return size;



This function is currently very complex, in terms of readability and performance. If the client had a loop that repeatedly needed the size of the linked list after modifying it, this would slow the code down. Instead, I suggest that you keep a private member variable called size in your class. Every time you add an element to your list, you increment this variable. Similarly, you decrement this variable when you delete something. Thus this function would then become O(1):



std::size_t size() const 
return size;



This matches the behavior of the STL. However, adding such a member could bloat your class, as @Frank pointed out. If you want to really follow the STL, then I would remove the size function altogether, as std::forward_list does for these reasons.






share|improve this answer























  • That makes sense. Thanks
    – Brady Dean
    Feb 24 at 17:20






  • 2




    I disagree. If you are regularly asking the size of a linked list, then you are doing something very wrong in the first place. So bloating the sizeof() and making the class invariant more complex just to accommodate what should be a code smell is pretty iffy on my book.
    – Frank
    Feb 24 at 18:33











  • @Frank I mean, most STL containers have a size() function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception is std::forward_list, but that removes the size function itself for the reason you said. The choice is between keeping a size function in constant time, or removing the function itself. Since OP is keeping it, I recommend changing it to perform in constant time.
    – Arnav Borborah
    Feb 24 at 18:41











  • To illustrate my point, OP's move semantics are currently correct, but adding a size member would require adding a move constructor and = operator.
    – Frank
    Feb 24 at 18:41






  • 3




    @Rakete1111 when moving the list, you need to reset the size of the original list to 0 since it will be empty after the move.
    – Frank
    Feb 25 at 18:25












up vote
2
down vote










up vote
2
down vote









While this is somewhat unrelated to what you want from this review, here is something I don't like:



std::size_t size() const 
if (!head)
return 0;


std::size_t size = 1;
Node<Type>* traverse = head.get();

while (traverse->next)
traverse = traverse->next.get();
size++;


return size;



This function is currently very complex, in terms of readability and performance. If the client had a loop that repeatedly needed the size of the linked list after modifying it, this would slow the code down. Instead, I suggest that you keep a private member variable called size in your class. Every time you add an element to your list, you increment this variable. Similarly, you decrement this variable when you delete something. Thus this function would then become O(1):



std::size_t size() const 
return size;



This matches the behavior of the STL. However, adding such a member could bloat your class, as @Frank pointed out. If you want to really follow the STL, then I would remove the size function altogether, as std::forward_list does for these reasons.






share|improve this answer















While this is somewhat unrelated to what you want from this review, here is something I don't like:



std::size_t size() const 
if (!head)
return 0;


std::size_t size = 1;
Node<Type>* traverse = head.get();

while (traverse->next)
traverse = traverse->next.get();
size++;


return size;



This function is currently very complex, in terms of readability and performance. If the client had a loop that repeatedly needed the size of the linked list after modifying it, this would slow the code down. Instead, I suggest that you keep a private member variable called size in your class. Every time you add an element to your list, you increment this variable. Similarly, you decrement this variable when you delete something. Thus this function would then become O(1):



std::size_t size() const 
return size;



This matches the behavior of the STL. However, adding such a member could bloat your class, as @Frank pointed out. If you want to really follow the STL, then I would remove the size function altogether, as std::forward_list does for these reasons.







share|improve this answer















share|improve this answer



share|improve this answer








edited Feb 25 at 23:59


























answered Feb 24 at 17:19









Arnav Borborah

654119




654119











  • That makes sense. Thanks
    – Brady Dean
    Feb 24 at 17:20






  • 2




    I disagree. If you are regularly asking the size of a linked list, then you are doing something very wrong in the first place. So bloating the sizeof() and making the class invariant more complex just to accommodate what should be a code smell is pretty iffy on my book.
    – Frank
    Feb 24 at 18:33











  • @Frank I mean, most STL containers have a size() function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception is std::forward_list, but that removes the size function itself for the reason you said. The choice is between keeping a size function in constant time, or removing the function itself. Since OP is keeping it, I recommend changing it to perform in constant time.
    – Arnav Borborah
    Feb 24 at 18:41











  • To illustrate my point, OP's move semantics are currently correct, but adding a size member would require adding a move constructor and = operator.
    – Frank
    Feb 24 at 18:41






  • 3




    @Rakete1111 when moving the list, you need to reset the size of the original list to 0 since it will be empty after the move.
    – Frank
    Feb 25 at 18:25
















  • That makes sense. Thanks
    – Brady Dean
    Feb 24 at 17:20






  • 2




    I disagree. If you are regularly asking the size of a linked list, then you are doing something very wrong in the first place. So bloating the sizeof() and making the class invariant more complex just to accommodate what should be a code smell is pretty iffy on my book.
    – Frank
    Feb 24 at 18:33











  • @Frank I mean, most STL containers have a size() function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception is std::forward_list, but that removes the size function itself for the reason you said. The choice is between keeping a size function in constant time, or removing the function itself. Since OP is keeping it, I recommend changing it to perform in constant time.
    – Arnav Borborah
    Feb 24 at 18:41











  • To illustrate my point, OP's move semantics are currently correct, but adding a size member would require adding a move constructor and = operator.
    – Frank
    Feb 24 at 18:41






  • 3




    @Rakete1111 when moving the list, you need to reset the size of the original list to 0 since it will be empty after the move.
    – Frank
    Feb 25 at 18:25















That makes sense. Thanks
– Brady Dean
Feb 24 at 17:20




That makes sense. Thanks
– Brady Dean
Feb 24 at 17:20




2




2




I disagree. If you are regularly asking the size of a linked list, then you are doing something very wrong in the first place. So bloating the sizeof() and making the class invariant more complex just to accommodate what should be a code smell is pretty iffy on my book.
– Frank
Feb 24 at 18:33





I disagree. If you are regularly asking the size of a linked list, then you are doing something very wrong in the first place. So bloating the sizeof() and making the class invariant more complex just to accommodate what should be a code smell is pretty iffy on my book.
– Frank
Feb 24 at 18:33













@Frank I mean, most STL containers have a size() function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception is std::forward_list, but that removes the size function itself for the reason you said. The choice is between keeping a size function in constant time, or removing the function itself. Since OP is keeping it, I recommend changing it to perform in constant time.
– Arnav Borborah
Feb 24 at 18:41





@Frank I mean, most STL containers have a size() function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception is std::forward_list, but that removes the size function itself for the reason you said. The choice is between keeping a size function in constant time, or removing the function itself. Since OP is keeping it, I recommend changing it to perform in constant time.
– Arnav Borborah
Feb 24 at 18:41













To illustrate my point, OP's move semantics are currently correct, but adding a size member would require adding a move constructor and = operator.
– Frank
Feb 24 at 18:41




To illustrate my point, OP's move semantics are currently correct, but adding a size member would require adding a move constructor and = operator.
– Frank
Feb 24 at 18:41




3




3




@Rakete1111 when moving the list, you need to reset the size of the original list to 0 since it will be empty after the move.
– Frank
Feb 25 at 18:25




@Rakete1111 when moving the list, you need to reset the size of the original list to 0 since it will be empty after the move.
– Frank
Feb 25 at 18:25












 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188246%2fraii-style-single-linked-list%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation