IntList
Specifications
There's a lot of useful information on this page, including some very helpful diagrams explaining the encoding of our
IntList
class at the bottom. Make sure you've read it all before you start coding!
This page describes the interface and encoding for the IntList
class you will be developing in this assignment.
Your implementation must support all the elements of the interface, and each element must have the specified complexity.
You may not change the names of anything in the interface, nor may you change the encoding. However, you are free (and encouraged!) to add private helper functions to your class.
The provided code already contains declarations for the interface and encoding.
Interfaces
Interfaces specify the provided operations.
IntList
Interface
Your linked-list class must be named IntList
and must support the following operations (in the description below, assume list
, list1
etc. are IntList
objects and \( n, m\ldots \) are the size of the lists):
- A default constructor †
- Creates an empty list.
- Requires \( \Theta(1) \) time.
list1 = list2
, the assignment operator. †- It destroys the old value of
list1
and replaces it with a duplicate oflist2
. - Requires \( \Theta(n+m) \) time.
- It destroys the old value of
- A copy constructor
- Copies all the integer values from an existing list into a new list.
- Requires \( \Theta(n) \) time.
- A destructor
- Destroys all the list elements and deallocates their heap memory.
- Requires \( \Theta(n) \) time.
list1.swap(list2)
†- Swaps
list1
andlist2
- Requires \( \Theta(1) \) (regardless of the lengths of the lists).
- Swaps
swap(list1,list2)
†- Performs the same swap operation as the member function above, but is provided as a symmetric-looking global function rather than a member function.
list.push_front(v)
- Pushes
v
(anint
) onto the front of the list. - Requires \( \Theta(1) \).
- Pushes
list.pop_front()
- Removes and returns a single
int
from the front of the list. - Requires \( \Theta(1) \).
- It is against the rules for users to call
pop_front
on an empty list.
- Removes and returns a single
list.push_back(v)
- Pushes
v
(anint
) onto the back of the list. - Requires \( \Theta(1) \) time.
- Pushes
list.size()
†- Returns number of elements in the list (as a
size_t
) - Requires \( \Theta(1) \) time.
- Returns number of elements in the list (as a
list.empty()
- Returns
true
if the list is empty. - Requires \( \Theta(1) \) time.
- Returns
list1 == list2
- Returns true if the contents of both lists are identical.
- Requires \( \Theta(1) \) time if the lists are of different lengths.
- Requires \( \mathrm{O}(n) \) time if the lists are the same length.
- More specifically, it takes \( \Theta(l) \) time where \( l \) is the length of the prefix of nodes that are equal. (e.g., if lists have different first elements, the algorithm only takes \( \Theta(1) \) time and if the lists are equal, it reqires \( \Theta(n) \) time.)
list1 != list2
. †- This trivial function returns
!(list1 == list2)
using the above function.
- This trivial function returns
- Two public types,
iterator
andconst_iterator
†- These types provide the standard operations of a “forward iterator” to allow users to access elements of the list in order.
- They are accessed outside the class as
IntList::iterator
andIntList::const_iterator
. - (See also the discussion of
IntList::Iter
below.)
list.begin()
- Returns an
iterator
set at the first element inlist
- When the list is empty, a past-the-end
iterator
is returned.
- When the list is empty, a past-the-end
- Returns an
- Requires \( \Theta(1) \) time to create the iterator.
list.begin() const
- Exactly like the above function, but for read-only lists; it returns a
const_iterator
.
- Exactly like the above function, but for read-only lists; it returns a
list.end()
- Returns a past-the-end
iterator
- It is completely legal to call
end()
on an empty list. - As also noted in the iterator section, it is against the rules for users to call
*
or++
on a past-the-end iterator.
- It is completely legal to call
- Requires \( \Theta(1) \) time to create the iterator.
- Returns a past-the-end
list.end() const
- Exactly like the above function, but for read-only lists; it returns a
const_iterator
.
- Exactly like the above function, but for read-only lists; it returns a
list.insert_after(i,v)
- Inserts
v
(anint
) integer immediately after the position indicated byi
(aniterator
).- It is against the rules to call
insert_after
with past-the-end iterator. Thus people are not allowed to callinsert_after
on an empty list.
- It is against the rules to call
- Requires \( \Theta(1) \) time.
- Inserts
† This code has been provided for you; you don’t need to modify it.
When you say “it's against the rules to do X”, what should happen if someone breaks the rules?
It's undefined behavior!
Yes. Just like last assignment, you can choose how to handle it. You can maximize speed and not check at all, or you can put something like an
assert(!empty());
to make sure everything is as it should be. Exceptions, andaffirm
are workable options, too.There are some benefits to knowing when things have gone wrong. So, if a usage error is easy to detect, I'd say it's worth detecting it. But if it's hard to detect, we have permission not to try.
IntList
's Iterator Interface
Our list provides two user-facing nested classes, IntList::iterator
and IntList::const_iterator
. A nested class is just a class that is defined within another class; we use the scope resolution operator ::
, as we have in the past, to specify that we want the iterator
class inside the IntList
class as opposed to, say, the std::vector<int>
class: IntList::iterator
vs. std::vector<int>::iterator
.
The first of these two classes is an alias for a private, nested class, IntList::Iter
. In other words, they are two names for the same thing; users write code using objects of the public type IntList::iterator
, with a lowercase i
, and we implement the private IntList::Iter
nested class. This way we retain our convention of capitalizing class names while also aligning with the C++ requirements for an iterator to work with standard-library algorithms.
The second class, IntList::const_iterator
is a type alias for const_adaptor<IntList::Iter>
, which is a wrapper around our IntList::Iter
class that causes it to behave exactly the same, except that *
returns a const int&
rather than an int&
. This allows us to provide a read-only iterator without duplicating the code of the IntList::Iter
class. You can ignore the details of how this second class is created, but it may be useful to know that we have set things up so that an IntList::iterator
can be assigned to an IntList::const_iterator
and the appropriate conversion will happen automatically.
Recall that an IntList::iterator
is a custom class that behaves like a pointer to an int
from the perspective of the operations it provides (*
, ++
, etc.) but is not actually a pointer to an int
; it is our own custom class. It might be tempting to say that the iterator “points to” something, but that might be confusing, so we'll adopt the following terminology:
- Internally, the iterator is set at a particular list node, but users cannot access the entire list node, only the
int
stored in it, so… - Externally, the user uses the iterator to access a particular
int
element of the list, and when they use*iter
, it refers to thatint
inside the node.
Our IntList::Iter
class provides the following operations, which all take \( \Theta(1) \) time:
- A default constructor. †
- It is against the rules for users to try to apply
*
,++
or==
to a default-constructed iterator. They may, however, assign it a new value.
- It is against the rules for users to try to apply
- A copy constructor. †
- An assignment operator. †
- A destructor. †
iter1 == iter2
anditer1 != iter2
, which test to see if two iterators are set to the exact same list element.- It is against the rules for users to compare iterators from different lists.
++iter
advances the iterator to the next list element (or to the past-the-end value).- It is against the rules for users to increment a past-the-end iterator, so your code doesn't have to handle this.
*iter
returns anint&
(so that the integer at the current position can be modified, if desired).- It is against the rules for users to use
*
on a past-the-end iterator.
- It is against the rules for users to use
When a node in the list is removed by pop_front
(or by assignment), any iterators that are set to that node are no longer considered valid (because the list node has been destroyed and they are the iterator equivalent of a dangling pointer!).
If an iterator isn't considered valid, it is against the rules to do anything with it other than assign a new (valid) value to it, or destroy it (e.g., by letting the variable go out of scope)—users may not apply *
or ++
to it, or even compare it with another iterator.
It is up to the user to make sure that they don't use an iterator that isn't valid anymore; your implementation doesn't need to do anything special to prevent misuse. But, like code written by other users, your tests must take care to obey all the rules, including not trying to use an iterator that is no longer valid.
But could I detect when an iterator stops being valid and give an error message if people try to use it?
Perhaps anything's possible with enough effort, but not all misuses of iterators are easy to detect. We say not to try to detect against-the-rules things like using an iterator after it isn't valid any more; just trust that the user won't do that.
The iterator should also provide the necessary information to work with standard-library algorithms, which means the class must include appropriate informational type definitions (see the header file for details). †
† This code has been provided for you; you don’t need to modify it.
Encodings
Recall that an encoding specifies how data is represented.
IntList
Encoding
The IntList
data structure is a linked list.
The IntList
object will be relatively small, and all the data will be in IntList::Node
objects on the heap.
The IntList
object will contain pointers to the first and last data element (if any), and a count of the object’s size. These extensions let the class provide efficient push_back
and size
operations.
The diagram below shows an empty IntList
:
and this diagram shows an IntList
with three elements (42, 24 and 36).
Notice that the list elements are stored in Node
s. A Node
is “just data”, it merely aggregates the data together—it isn't a self-contained class with its own operations. No functions in the interface give users direct access to Node
s themselves.
More concretely, our IntList
class has the following private data members:
// Data members
Node* front_ = nullptr; // Current head of list
Node* back_ = nullptr; // Current tail of list
size_t size_ = 0; // Current size of list
// ^-------- by specifying initializations here, we can avoid
// needing to specify a member initialization list.
We've defined a nested struct
for the Node
type. In accordance with C++ conventions, you may not add any member functions to this struct
.
struct Node {
int value_ = 0; // Value of the list element
Node* next_ = nullptr; // Next list node
};
struct
vs class
IntList::Node
is defined as a struct
. Technically, the only difference between a struct
and a class
is that a struct
sets members to public
when not specified while a class
defaults to private. So, just like a class it has data members.
However, by convention, in C++, struct
s are typically used for “plain old data” that needs to be managed by other code (i.e., it doesn't have member functions to manage itself).
Okay, but how to I create a
Node
? We haven't defined a constructor for it!C++ actually automatically defines some useful constructors for
struct
s.
In C++, a struct
automatically has three constructors: a default constructor, a copy constructor, and a parameterized constructor that takes one argument for each data member. So, you can create a new Node
on the heap with curly-brace syntax, like this:
Node* myNodePtr = new Node{42, nullptr};
What about making a
Node
on the stack?Yes, we can make
Node
s on the stack just like other objects, but actually, you shouldn't need to that in this assignment. You'll only need to makeNode
s on the heap that become part of a list and stick around for as long as they need to.If we put our list
Node
s on the stack, they'd vanish when the function returns, and we'd be left with a dangling pointer!
So remember, you can use curly-brace constructor syntax with a struct
, even without writing any constructors. Just put the constructor arguments in the same order as the data members.
IntList::Iter
Encoding
The list’s iterator provides a way to access the value stored in an element of a list, and it is encoded as a pointer to a Node
.
The diagram below shows iterator that is set at the second node of our list, allowing access to both the value stored in that element, and the next node along in the list.
So I could access the value the iterator is set at via
*current_.value_
.Actually, no. If you want to use
*
, you'd need to say(*current_).value_
.But it's better to say
current_->value_
.Remember though, this is what you say inside your iterator code.
(When logged in, completion status appears here.)