A Better Linked List Class ########################## .. wordcount:: .. vim:filetype=rst spell: .. note:: This lecture covers advanced concepts in C++, not really seen in an introductory C++ class like ours, but it they worth taking a quick look at. Let's build the simple Linked List class again, and this time set up some new tricks that C++ provides. This list will be a little different. We will assign an ID number to each node, in addition to the real value we want to store in the list. The reason for this will become apparent later. Here is a diagram of what we have been building so far: .. circuits:: :align: center :width: 500 :tikzlibs: calc,shapes.multipart,chains,arrows [list/.style={rectangle split, rectangle split parts=2, draw, rectangle split horizontal}, >=stealth, start chain] \node[list,on chain] (A) {12}; \node[list,on chain] (B) {99}; \node[list,on chain] (C) {37}; \node[on chain,draw,inner sep=6pt] (D) {}; \draw (D.north east) -- (D.south west); \draw (D.north west) -- (D.south east); \draw[*->] let \p1 = (A.two), \p2 = (A.center) in (\x1,\y2) -- (B); \draw[*->] let \p1 = (B.two), \p2 = (B.center) in (\x1,\y2) -- (C); \draw[*->] let \p1 = (C.two), \p2 = (C.center) in (\x1,\y2) -- (D); This setup is not exactly memory efficient. Each integer takes four bytes, and each pointer takes 8 bytes. So managing 1000 integers will take 12,000 bytes, not the 4000 bytes really needed for the data. Arrays, on the other hand take only as much storage as the data, so we could store all those integers in just 4000 bytes. Maybe we can do better! Let's work through our list design strategy. The Node Class ************** We need a simple node to hold our data items (and the ID). .. literalinclude:: code/include/Node.h :linenos: :caption: Node.h Not much new here, except for the ID attribute. The ID is just a number that needs to be unique to each node we create. The obvious place to set up this number is in the constructor. However, we want the system to manage the unique ID for us. We can do that if we set up a ``class variable``, like this: See that ``static`` thing at the bottom of this class? That keyword tells the compiler to build exactly one of these attributes. All instances of the class can access this attribute, but when they do, they will all access the same variable. We will teach our constructor to handle this static variable and use it to assign a unique ID to each object that gets created! Since we know we should be testing code as we write it, here is a test file that will exercise this ``Node`` class: .. literalinclude:: code/tests/test_node.cpp :linenos: :caption: tests/test_node.cpp The first test in this code just makes sure that when we allocate a new ``Node``, we get a valid address for it. Not much of a test, but it is start. The second test is supposed to make sure we get unique IDs for each ``Node`` object we create. This test is harder than you might suspect. We will eventually have lots of tests that create ``Node`` objects, and we cannot be sure in what order those tests will run. For that reason, it will not be possible to create a node in this test and know exactly what ID it will have. What we can do it create two nodes and make sure the ID for each increases by one. Here is our first implementation of the Node class: .. literalinclude:: code/lib/Node.cpp :linenos: :caption: lib/Node.h Do you see how we manage our ID here? .. note:: The tests run at this point all passed, so I will not show you that output. The LinkedList Class ******************** Now that we have a ```Node`` we can use, lets build our first LinkedList setup: .. literalinclude:: code/include/LinkedList.h :linenos: :caption: include/LinkedList.h There are a few things to note here. First, we are managing pointers to the front and the end of the list. Tracking these places in the list will prove handy later, when we use this class. We also have provided new accessor methods: * getItemByID - searches for an item using the node ID. * getHeadID - this returns the ID of the node currently at the front of the list. This is use din testing. For this version of our class, the ``insert`` method will always add a new item at the front of the list. Here is a test set for this list: .. literalinclude:: code/tests/test_linked_list.cpp :linenos: :caption: tests/test_linked_list.cpp These tests should be easy to follow, after looking at the tests for the ``Node`` class previously. Here is the implementation of the ``LinkedList`` class: .. literalinclude:: code/lib/LinkedList.cpp :linenos: :caption: lib/LinkedList.cpp Not much is really new here. Time to change that! Reviewing the Node Class ************************ The only reason for building a ``Node`` class is to set up a container that will hold the data we need in the list. All of that code is really something the builder of the ``LinkedList`` class should own. No other part of the program should ness around with ``Nodes``. This is especially true is the container we are building (the list) is to be a general purpose one, with features designed to make it useful in our project. We have already seen that we can build our I35 simulator using an array of cars, and we could use a linked list of cars as well. Does the user of our code even need to know how we are managing a pile of cars at all? The answer is a bit NO. If we want to start off with arrays, that is a design decision, and has nothing to do with the rest of the code. Let's set up a new container that can manage a bunch of cars! Cars Class ========== We need a home for a car, and a way to access an individual car. We want to be able to add cars to the container, and fetch then so we can access their attributes when needed. How the cars are managed is not relevant to our bigger project. Here is a new Car class: .. literalinclude:: code/include/Car.h :linenos: :caption: include/Car.h As a developer charged with building this part of the project, your first job is to fully understand what you are supposed to build. The class header file tells you what your class needs to do, but how you do that is up to you. Now, your first idea may well be to set up an array to manage these cars. You also might decide to create a ``Car`` class to manage the data you need to store for each car. That is fine, and may do the job nicely. We have a problem here, though. How many cars are we supposed to manage. I know! 500! Tilt! As soon as you pick some random number, you have made a serious error. The **ONLY** time it makes sense to pick a number like this is after you and the users of your code agree on that number! You are not in charge of a decision like this! The Sky is the Limit -------------------- Cars will be added (and deleted) as the program runs! You really have no idea how many cars to handle. Can an array still do the job? Well, if we think a bit, maybe we can deal with this. Why does an array need to be in just one big chunk of memory? What if we created a bunch of small chunks, and linked those chunks together? The result would be more memory efficient, but we have a complication to deal with. Simple indexes no longer work. Fortunately, C++ can help here. The Subscript Operator ********************** Remember those brackets we use for subscripting an array? It turns out those are just "operators" line addition and subtraction. C++ lets you set up code that will be fired off when you add brackets after an object's name! .. code-block:: c++ class MyInts { private: int m_list[10]; public: int& operator[] (const int index); }; int& MyInts::operator[] (const int index) { return m_list[index]; } Notice that the new operator returns a reference. That is important, since we might want to assign a value into the array using this kind of code: .. code-block:: c++ MyInts data; data[2] = 42; C++ requires that the thing on the left-hand side of an assignment statement is actually referring to a chunk of read memory. (In C++, they call the left-hand side an ``lvalue``). That means it has a real address, The object obviously has one, but we are using a subscript after that. So ``data[2]`` actually runs a method in our class, and that function must return an address that can be used to place the value in memory! Once we know we can do things like this, we can add subscripts to any class, and set up the code to locate the required piece of data, no matter where we decide to hide it. The user of our class never needs to know anything about the trickery we are setting up inside. Vectors ******* C++ actually has such a storage container in the ``Standard Template Library``. It is called a ``vector``. We will look at that later.