RAII-style single-linked list
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
6
down vote
favorite
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;
;
c++ linked-list pointers c++17 raii
add a comment |Â
up vote
6
down vote
favorite
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;
;
c++ linked-list pointers c++17 raii
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
add a comment |Â
up vote
6
down vote
favorite
up vote
6
down vote
favorite
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;
;
c++ linked-list pointers c++17 raii
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;
;
c++ linked-list pointers c++17 raii
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
add a comment |Â
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
add a comment |Â
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()
?
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
add a comment |Â
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.
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 asize()
function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception isstd::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
 |Â
show 6 more comments
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()
?
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
add a comment |Â
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()
?
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
add a comment |Â
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()
?
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()
?
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
add a comment |Â
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
add a comment |Â
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.
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 asize()
function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception isstd::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
 |Â
show 6 more comments
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.
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 asize()
function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception isstd::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
 |Â
show 6 more comments
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.
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.
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 asize()
function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception isstd::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
 |Â
show 6 more comments
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 asize()
function that returns its size in constant time (from C++11), so I'm trying to maintain that. The only exception isstd::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
 |Â
show 6 more comments
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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