HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Creating linked list data with iterator pattern

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
iteratorcreatingwithdatalistlinkedpattern

Problem

I've created a linked list to use it in SquareList. I've started to create a linked list based on just that. I want my class to follow the iterator pattern. How can it be improve to use vector iterator?

```
#ifndef LINKEDLIST_HPP
#define LINKEDLIST_HPP

#include

template
class Linkedlist
{
private:
// The basic doubly linked Linkedlist node.
// Nested inside of Linkedlist, can be public
// because the Node is itself private
struct Node
{
Object _data;
Node *_prev;
Node *_next;

Node( const Object & d = Object( ), Node p = NULL, Node n = NULL )
: _data( d ), _prev( p ), _next( n ) { }
};

public:

public:
Linkedlist( )
{ init( ); }

~Linkedlist( )
{
clear( );
delete _head;
delete _tail;
}

Linkedlist( const Linkedlist & rhs )
{
init( );
*this = rhs;
}

const Linkedlist & operator= ( const Linkedlist & rhs )
{
if( this == &rhs )
return *this;
clear( );
for( const_iterator itr = rhs.begin( ); itr != rhs.end( ); ++itr )
push_back( *itr );
return *this;
}

// Return iterator representing beginning of Linkedlist.
// Mutator version is first, then accessor version.
iterator begin( )
{ return iterator( _head->_next ); }

const_iterator begin( ) const
{ return const_iterator( _head->_next ); }

// Return iterator representing endmarker of Linkedlist.
// Mutator version is first, then accessor version.
iterator end( )
{ return iterator( _tail ); }

const_iterator end( ) const
{ return const_iterator( _tail ); }

// Return number of elements currently in the Linkedlist.
int size( ) const
{ return _size; }

// Return true if the Linkedlist is empty, false otherwise.
bool empty( ) const
{ return size( ) == 0; }

void clear( )
{
while( !empty( ) )

Solution

You define the copy constructor in terms of the assignment operator

Linkedlist( const Linkedlist & rhs )
{
    init( );
    *this = rhs;
}


It is more traditional to do it the other way around. Normally you define the assignment in terms of the copy constructor. The idiom is called "Copy and Swap"

Linkedlist(Linkedlist const& rhs)
{
    // Create a copy
}

Linkedlist& operator=(Linkedlist rhs) // pass by value to get copy
{
    rhs.swap(*this);   // swap the copy with this.
    return *this;      // As rhs leaves scope with the old values
}                      // it is automatically deleted.


There are a couple of problems with doing it the other way around.

You do not provide the strong exception guarantee. On an assignment you destroy the content of you list. Then start to populate it with new values. If at any point copying fails you are left with a half complete object and no way to roll back to the original object.

Assignment should fall into three distinct phases.

A = B;


-
Create a copy of the object being assigned from B into a temporary object. This will be the new value that A holds once everything works. If this copy fails then the value of A should not be changed.

-
Swap the internal state of A with the temporary object. Since a swap should never fail this has no danger. A will now hold a copy of B and the temporary object will hold the old value of A.

-
Destroy the old object. If this fails it does not matter. As the state of A is now consistent.

const Linkedlist & operator= ( const Linkedlist & rhs )
{
if( this == &rhs )
return *this;
clear( );

At this point A has been cleared. Thus if the folloping loop fails you have no way to recover the original value of A. This will leave your program in a funny state. You have provided The Basic Exception Guarantee in that the object is not invalid. But this is not usually acceptable.

for( const_iterator itr = rhs.begin( ); itr != rhs.end( ); ++itr )
        push_back( *itr );

    return *this;
}


This seems a bit over-elaborate!

Object & front( )
  { return *begin( ); }


You are creating an iterator just to de-reference it! Why not just

Object & front( )
  { return head->next.data; }


You should also put a comment that it is undefined behavior when the list is empty. Best to document these things up front.x

Not to sure you need to define two different classes for iterator and const_iterator. Seems like a lot of duplicated code.

Also you iterator does not meet even the requirements to trivial iterator as you are missing operator ->. See http://www.sgi.com/tech/stl/table_of_contents.html specifically look at the iterator section. I presume you are actually trying to implement the concept of Bi-Directional iterator the definition can be found here http://www.sgi.com/tech/stl/BidirectionalIterator.html

In addition your container object is supposed to provide some specific types that allow the user of your class to obtain information about the content type. Requirements can be found here: http://www.sgi.com/tech/stl/Container.html

See here for an example of an iterator: https://codereview.stackexchange.com/a/9399/507 (though I did not make the container as well as I could because I did not specify the types I should have).

See here for an example of a linked list using a single sentinel.

https://codereview.stackexchange.com/a/126007/507

Code Snippets

Linkedlist( const Linkedlist & rhs )
{
    init( );
    *this = rhs;
}
Linkedlist(Linkedlist const& rhs)
{
    // Create a copy
}

Linkedlist& operator=(Linkedlist rhs) // pass by value to get copy
{
    rhs.swap(*this);   // swap the copy with this.
    return *this;      // As rhs leaves scope with the old values
}                      // it is automatically deleted.
for( const_iterator itr = rhs.begin( ); itr != rhs.end( ); ++itr )
        push_back( *itr );


    return *this;
}
Object & front( )
  { return *begin( ); }
Object & front( )
  { return head->next.data; }

Context

StackExchange Code Review Q#40922, answer score: 8

Revisions (0)

No revisions yet.