I'm really new to C++ (very surprised at the leap from Python and others to C++!). The code compiles and works well. This took all day to get to work, and I've the tendency to move on after I get [what I see as] basic functionality. I'd really like to become a good programmer, though.
I assume there are a ton of things I'm doing wrong, but I don't know what they are.
Although I've read a lot about them, I still don't fully understand when I should use a pointer. I know what they are, and what its difference is with a reference—but I haven't grokked why I should use one over the other.
Also, everything I've read about implementing the STL iterator (and I've read quite a bit on that) confuses the hell out of me, so I wrote a pretty basic one instead.
//====================
// Include Guard
#ifndef __LINKEDLIST_HPP_INCLUDED__
#define __LINKEDLIST_HPP_INCLUDED__
//===================
// Included Dependencies
#include <iostream>
#include <iterator>
//====================
// Forwarded Dependencies
//====================
// Classes (Header)
template<class T> class Node {
public:
Node();
Node(T object);
T getObject();
Node* next_;
private:
T object_;
};
template<class T> class ListIterator {
public:
ListIterator(Node<T>* head_node);
T next();
private:
Node<T>* current_node;
};
template<class T> struct LinkedList {
public:
LinkedList (T initialObject);
~LinkedList();
void addObject (T addedObject);
void removeObject (int position);
int getSize();
ListIterator<T> getIterator();
private:
Node<T>* head_;
Node<T>* current_;
int size_;
};
//====================
// Class (Instantiation)
template<class T> Node<T>::Node() {};
template<class T> Node<T>::Node(T object) { object_ = object; };
template<class T> T Node<T>::getObject() { return object_; };
template<class T> ListIterator<T>::ListIterator(Node<T>* head_node) {
current_node = head_node;
};
template<class T> T ListIterator<T>::next() {
Node<T>* tmp_node = current_node;
current_node = current_node->next_;
return current_node->getObject();
};
template<class T> LinkedList<T>::LinkedList(T initialObject) {
head_ = new Node<T>;
current_ = new Node<T>(initialObject);
head_->next_ = current_;
current_->next_ = head_;
size_ = 1;
};
// Destructor
template<class T> LinkedList<T>::~LinkedList() {
for (int ii = 0; ii <= size_-1; ii++) {
Node<T>* tmpNode = head_->next_;
head_->next_ = head_->next_->next_;
delete tmpNode;
};
delete head_;
};
// Add object. Link it to head to make this circular
template<class T> void LinkedList<T>::addObject(T addedObject) {
Node<T>* tmpNode_ = current_;
current_->next_ = new Node<T>(addedObject);
current_ = current_->next_;
current_->next_ = head_;
size_ += 1;
};
// Remove the node in the spot specified by 'position'
template<class T> void LinkedList<T>::removeObject(int position) {
if (position <= 0) { std::cout << "ERROR: Invalid position."; }
else if (size_ > 0 && position <= size_) {
Node<T>* tmpNode_ = head_;
for (int ii = 0; ii < position-1; ii++) {
tmpNode_ = tmpNode_->next_;
};
delete tmpNode_->next_;
tmpNode_->next_ = tmpNode_->next_->next_;
size_ -= 1;
}
else if (size_ > 0 && position > size_) {
std::cout << "ERROR: LinkedList contains less than " << position
<< " elements\n";
}
else if (size_ == 0) { std::cout << "ERROR: LinkedList is empty\n"; }
};
// Return size of Linked List
template<class T> int LinkedList<T>::getSize() { return size_; };
// Return iterator
template<class T> ListIterator<T> LinkedList<T>::getIterator() {
return ListIterator<T>(head_);
};
#endif
1 Answer 1
Node
- There is no need to make
Node
public.LinkedList
andListIterator
need it but client of your library doesn't. So consider hiding it from him. For example by definingNode
within theLinkedList
class. Node()
- It would be cleaner if
next_
would be set tonullptr
(if in C++11 or above) orNULL
/0
(otherwise). - There should be no use for default constructor anyway. (See below notes on
LinkedList<T>::head_
.) In fact use of this constructor requires typeT
to be DefaultConstructible while there is no need to require that.
- It would be cleaner if
Node(T object)
- Use member initializer list instead of assigning to members in constructor's body.
- It would be cleaner if
next_
would be set tonullptr
(if in C++11 or above) orNULL
/0
(otherwise). - Change argument to
const T&
if you are pre-C++11. - Otherwise use move semantics when constructing
object_
fromobject
. And also possibly addconst T&
overload anyway.
getObject()
- Return
T&
instead ofT
as it would not require copying of the object. - And add
const
overload which returnsconst T&
.
- Return
next_
- Should not be a
public
member! Make it private and grant toLinkedList
andListIterator
access to for example usingfriend
.
- Should not be a
- It could be useful to forbid copying of
Node
objects as it seems there is no use in that. To do that useboost::noncopyable
if you have Boost. If you don't then either explicitly "delete" copy constructor and assignment operator (if in C++11 or above) or make themprivate
without defining them.
- There is no need to make
ListIterator
- As you noted yourself your iterator differs significantly from what C++ considers and iterator. This means in particular that it will not be usable with any STL function. Or other libraries that use "normal iterators".
- As a side note I will mention that
boost::iterator_facade
makes it much easier to write proper iterator. But it requires use of Boost. - With your current design as it is how will you know that the iteration ended? There is no method in
ListIterator
that says that. With your current implementation (of cyclic list - see comments inLinkedList
) any iteration would be infinite unless you would count elements yourself during iteration and stop atgetSize
. ListIterator(Node<T>* head_node)
- Use member initializer list instead of assigning to members in constructor's body.
next()
- Implementation incorrectly returns value from
current_node
. It seems that instead it should return value fromtmp_node
. - Return
T&
instead ofT
as it would not require copying of the object. (But to do that you have to do the same forNode<T>::getObject()
as commented above.)
- Implementation incorrectly returns value from
- Consider having also a
ConstListIterator
which usesconst Node<T>*
and returnsconst T&
fromnext()
. This would allow to iterate overconst LinkedList
object. With some template magic this could be done with single implementation.
LinkedList
- From implementation it seems to be a cyclic list. Was this intended?
- I don't follow the idea behind
head_
node. I think that it is not needed. Not to mention that it's value could be undefined while it will show up during iteration. - The class would be a bit more usable if you could add element at arbitrary position. Given by integer (to make your design consistent) or iterator (to be consistent with previous comment).
- The class would be a bit more usable if you could remove element based on iterator. Or else allow the iterator to return it's position as integer. But remove by iterator is more in line with C++ (STL) style.
- The class would be a bit more usable if it allowed also
const
iteration. SogetIterator()
should have aconst
overload returningconst
iterator (as commneted above). - Size in C++ is usually expressed using
std::size_t
type. So should be also positions in your case. - There should be a default constructor making an empty list.
LinkedList(T initialObject)
- Consider making it
explicit
so that it cannot be used for conversion. - Add also second argument being count of initial elements. And default it to
1
. It would make the constructor a bit more flexible and little cost. (And also it will match typical STL container constructors.) - Change argument to
const T&
if you are pre-C++11. - Otherwise use move semantics when constructing
object_
fromobject
. And also possibly addconst T&
overload anyway. - Use member initializer list instead of assigning to members in constructor's body.
- Consider making it
- You should properly define copy constructor and assignment operator. Or at least forbid them (as already mentioned for
Node
). - Consider adding other constructors. For example from a range of elements of other list (by positions or by iterators).
~LinkedList()
<= size_-1
in loop condition is risky. Once you change to (unsigned
)std::size_t
(as commented above)size_-1
forsize_ == 0
would wrap around0
and result in maximal positive value. The loop would be infinite. On the other hand note that alternative< size_
results in infinite loop forsize_
being already maximal positive value. (Which however seems less likely than being0
.)- In loop use
++ii
rather thanii++
. On modern compilers with integers it doesn't matter really. But it is cleaner to write so. And could make a difference ifii
would be an iterator rather than integer. So it is better to have good habits.
addObject(T addedObject)
- Unused
tmpNode_
local variable. - I would write
++size_
instead ofsize_ += 1
. Feels more natural. But in the end there should be no difference. - You are making no check against exceeding maximal positive value for
size_
. - Change argument to
const T&
if you are pre-C++11. - Otherwise use move semantics when constructing
Node<T>
fromaddedObject
. And also possibly addconst T&
overload anyway.
- Unused
removeObject(int position)
- Instead of writing to
cout
use exceptions. There are also other means (like returning error code). But outputting tocout
is of no use. (Andcerr
seems more adequate anyway...) < position-1
in loop condition is risky. Once you change to (unsigned
)std::size_t
(as commented above)position-1
forposition == 0
would wrap around0
and result in maximal positive value. The loop would be go crazy (although it would not be infinite). Instead of that you could start withii = 1
.- In loop use
++ii
rather thanii++
. On modern compilers with integers it doesn't matter really. But it is cleaner to write so. And could make a difference ifii
would be an iterator rather than integer. So it is better to have good habits. - You could extract the loop fragment to separate (
private
) method which returnsNode<T>*
of given position. It would make code somewhat cleaner. And also you could reuse the method in other functions that I recommended to add (in comments above). - You should first store
tmpNode_->next_
in a local variable. Then settmpNode_->next_
totmpNode_->next_->next_
. And only thendelete
the local variable. Current code usesnext_
of already deleted object and sooner or later will crash on that. - I would write
--size_
instead ofsize_ -= 1
. Feels more natural. But in the end there should be no difference. - This method is badly implemented anyway. For example on a list constructed with the single element constructor calling
removeObject(1)
will actuallydelete
the node fromhead_
but will never updatehead_
member. Maybe changing the condition toposition < size_
(and next toposition >= size_
) would correct it. But since the idea ofhead_
is likely wrong this method would change anyway.
- Instead of writing to
getSize()
- Should be a
const
method as it doesn't change the object.
- Should be a
There are also other possible extensions. For example you could add allocator support. Or allow in-place construction of elements. But those are more advanced topics. Maybe save them for later.
-
\$\begingroup\$ I thought this was going to go unnoticed! The cyclic iterator was purposeful; the objects I'm going to use this for will all have unique ID numbers, so the iterator will end when the ID of one of the objects repeats. Thanks so much for your corrections—I'll start working on them immediately. \$\endgroup\$AmagicalFishy– AmagicalFishy2015年08月14日 18:23:15 +00:00Commented Aug 14, 2015 at 18:23
-
\$\begingroup\$ One more question: If I define Node within Linked List—then I'd have to define the iterator within Linked List too, right? (If I did that, wouldn't Node and the iterator have to be public anyway?) \$\endgroup\$AmagicalFishy– AmagicalFishy2015年08月14日 19:02:42 +00:00Commented Aug 14, 2015 at 19:02
-
\$\begingroup\$ @AmagicalFishy If
Node
is madeprivate
withinLinkedList
then eitherListIterator
must be defined withinLinkedList
or must be afriend
of it. \$\endgroup\$Adam Badura– Adam Badura2015年08月15日 11:41:08 +00:00Commented Aug 15, 2015 at 11:41
Explore related questions
See similar questions with these tags.