Why do linked lists use pointers instead of storing nodes inside of nodes

C++PointersLinked List

C++ Problem Overview


I've worked with linked lists before extensively in Java, but I'm very new to C++. I was using this node class that was given to me in a project just fine

class Node
{
  public:
   Node(int data);

   int m_data;
   Node *m_next;
};

but I had one question that wasn't answered very well. Why is it necessary to use

Node *m_next;

to point to the next node in the list instead of

Node m_next;

I understand that it is better to use the pointer version; I'm not going to argue facts, but I don't know why it's better. I got a not so clear answer about how the pointer is better for memory allocation, and I was wondering if anyone here could help me understand that better.

C++ Solutions


Solution 1 - C++

It's not just better, it's the only possible way.

If you stored a Node object inside itself, what would sizeof(Node) be? It would be sizeof(int) + sizeof(Node), which would be equal to sizeof(int) + (sizeof(int) + sizeof(Node)), which would be equal to sizeof(int) + (sizeof(int) + (sizeof(int) + sizeof(Node))), etc. to infinity.

An object like that can't exist. It's impossible.

Solution 2 - C++

In Java

Node m_node

stores a pointer to another node. You don't have a choice about it. In C++

Node *m_node

means the same thing. The difference is that in C++ you can actually store the object as opposed to a pointer to it. That's why you have to say you want a pointer. In C++:

Node m_node

means store the node right here (and that clearly can't work for a list - you end up with a recursively defined structure).

Solution 3 - C++

C++ is not Java. When you write

Node m_next;

in Java, that is the same as writing

Node* m_next;

in C++. In Java, the pointer is implicit, in C++ it is explicit. If you write

Node m_next;

in C++, you put an instance of Node right there inside the object that you are defining. It is always there and cannot be omitted, it cannot be allocated with new and it cannot be removed. This effect is impossible to achieve in Java, and it is totally different from what Java does with the same syntax.

Solution 4 - C++

You use a pointer, otherwise your code:

class Node
{
   //etc
   Node m_next; //non-pointer
};

…would not compile, as the compiler cannot compute the size of Node. This is because it depends on itself — which means the compiler cannot decide how much memory it would consume.

Solution 5 - C++

The latter (Node m_next) would have to contain the node. It wouldn't point to it. And there would then be no linking of elements.

Solution 6 - C++

The approach that you describe is compatible not only with C++, but also with its (mostly) subset language C. Learning to develop a C-style linked-list is a good way to introduce yourself to low-level programming techniques (such as manual memory management), but it generally is not a best-practice for modern C++ development.

Below, I have implemented four variations on how to manage a list of items in C++.

  1. raw_pointer_demo uses the same approach as yours -- manual memory management required with the use of raw pointers. The use of C++ here is only for syntactic-sugar, and the approach used is otherwise compatible with the C language.
  2. In shared_pointer_demo the list management is still done manually, but the memory management is automatic (doesn't use raw pointers). This is very similar to what you have probably experienced with Java.
  3. std_list_demo uses the standard-library list container. This shows how much easier things get if you rely on existing libraries rather than rolling your own.
  4. std_vector_demo uses the standard-library vector container. This manages the list storage in a single contiguous memory allocation. In other words, there aren't pointers to individual elements. For certain rather extreme cases, this may become significantly inefficient. For typical cases, however, this is the recommended best practice for list management in C++.

Of note: Of all of these, only the raw_pointer_demo actually requires that the list be explicitly destroyed in order to avoid "leaking" memory. The other three methods would automatically destroy the list and its contents when the container goes out of scope (at the conclusion of the function). The point being: C++ is capable of being very "Java-like" in this regard -- but only if you choose to develop your program using the high-level tools at your disposal.


/*BINFMTCXX: -Wall -Werror -std=c++11
*/

#include <iostream>
#include <algorithm>
#include <string>
#include <list>
#include <vector>
#include <memory>
using std::cerr;

/** Brief   Create a list, show it, then destroy it */
void raw_pointer_demo()
{
    cerr << "\n" << "raw_pointer_demo()..." << "\n";

    struct Node
    {
        Node(int data, Node *next) : data(data), next(next) {}
        int data;
        Node *next;
    };

    Node * items = 0;
    items = new Node(1,items);
    items = new Node(7,items);
    items = new Node(3,items);
    items = new Node(9,items);

    for (Node *i = items; i != 0; i = i->next)
        cerr << (i==items?"":", ") << i->data;
    cerr << "\n";

    // Erase the entire list
    while (items) {
        Node *temp = items;
        items = items->next;
        delete temp;
    }
}

raw_pointer_demo()...
9, 3, 7, 1

/** Brief   Create a list, show it, then destroy it */
void shared_pointer_demo()
{
    cerr << "\n" << "shared_pointer_demo()..." << "\n";

    struct Node; // Forward declaration of 'Node' required for typedef
    typedef std::shared_ptr<Node> Node_reference;

    struct Node
    {
        Node(int data, std::shared_ptr<Node> next ) : data(data), next(next) {}
        int data;
        Node_reference next;
    };

    Node_reference items = 0;
    items.reset( new Node(1,items) );
    items.reset( new Node(7,items) );
    items.reset( new Node(3,items) );
    items.reset( new Node(9,items) );

    for (Node_reference i = items; i != 0; i = i->next)
        cerr << (i==items?"":", ") << i->data;
    cerr<<"\n";

    // Erase the entire list
    while (items)
        items = items->next;
}

shared_pointer_demo()...
9, 3, 7, 1

/** Brief   Show the contents of a standard container */
template< typename C >
void show(std::string const & msg, C const & container)
{
    cerr << msg;
    bool first = true;
    for ( int i : container )
        cerr << (first?" ":", ") << i, first = false;
    cerr<<"\n";
}

/** Brief  Create a list, manipulate it, then destroy it */
void std_list_demo()
{
    cerr << "\n" << "std_list_demo()..." << "\n";

    // Initial list of integers
    std::list<int> items = { 9, 3, 7, 1 };
    show( "A: ", items );

    // Insert '8' before '3'
    items.insert(std::find( items.begin(), items.end(), 3), 8);
    show("B: ", items);

    // Sort the list
    items.sort();
    show( "C: ", items);

    // Erase '7'
    items.erase(std::find(items.begin(), items.end(), 7));
    show("D: ", items);

    // Erase the entire list
    items.clear();
    show("E: ", items);
}

std_list_demo()...
A:  9, 3, 7, 1
B:  9, 8, 3, 7, 1
C:  1, 3, 7, 8, 9
D:  1, 3, 8, 9
E:

/** brief  Create a list, manipulate it, then destroy it */
void std_vector_demo()
{
    cerr << "\n" << "std_vector_demo()..." << "\n";

    // Initial list of integers
    std::vector<int> items = { 9, 3, 7, 1 };
    show( "A: ", items );

    // Insert '8' before '3'
    items.insert(std::find(items.begin(), items.end(), 3), 8);
    show( "B: ", items );

    // Sort the list
    sort(items.begin(), items.end());
    show("C: ", items);

    // Erase '7'
    items.erase( std::find( items.begin(), items.end(), 7 ) );
    show("D: ", items);

    // Erase the entire list
    items.clear();
    show("E: ", items);
}

std_vector_demo()...
A:  9, 3, 7, 1
B:  9, 8, 3, 7, 1
C:  1, 3, 7, 8, 9
D:  1, 3, 8, 9
E:

int main()
{
    raw_pointer_demo();
    shared_pointer_demo();
    std_list_demo();
    std_vector_demo();
}

Solution 7 - C++

Overview

There are 2 ways to reference and allocate objects in C++, while in Java there is only one way.

In order to explain this, the following diagrams, show how objects are stored in memory.

1.1 C++ Items without pointers

class AddressClass
{
  public:
    int      Code;
    char[50] Street;
    char[10] Number;
    char[50] POBox;
    char[50] City;
    char[50] State;
    char[50] Country;
};

class CustomerClass
{
  public:
    int          Code;
    char[50]     FirstName;
    char[50]     LastName;
    // "Address" IS NOT A pointer !!!
    AddressClass Address;
};

int main(...)
{
   CustomerClass MyCustomer();
     MyCustomer.Code = 1;
     strcpy(MyCustomer.FirstName, "John");
     strcpy(MyCustomer.LastName, "Doe");
     MyCustomer.Address.Code = 2;
     strcpy(MyCustomer.Address.Street, "Blue River");
     strcpy(MyCustomer.Address.Number, "2231 A");
   
   return 0;
} // int main (...)

.......................................
..+---------------------------------+..
..|          AddressClass           |..
..+---------------------------------+..
..| [+] int:      Code              |..
..| [+] char[50]: Street            |..
..| [+] char[10]: Number            |..
..| [+] char[50]: POBox             |..
..| [+] char[50]: City              |..
..| [+] char[50]: State             |..
..| [+] char[50]: Country           |..
..+---------------------------------+..
.......................................
..+---------------------------------+..
..|          CustomerClass          |..
..+---------------------------------+..
..| [+] int:      Code              |..
..| [+] char[50]: FirstName         |..
..| [+] char[50]: LastName          |..
..+---------------------------------+..
..| [+] AddressClass: Address       |..
..| +-----------------------------+ |..
..| | [+] int:      Code          | |..
..| | [+] char[50]: Street        | |..
..| | [+] char[10]: Number        | |..
..| | [+] char[50]: POBox         | |..
..| | [+] char[50]: City          | |..
..| | [+] char[50]: State         | |..
..| | [+] char[50]: Country       | |..
..| +-----------------------------+ |..
..+---------------------------------+..
.......................................

Warning: The C++ syntax used in this example, is similar to the syntax in Java. But, the memory allocation is different.

1.2 C++ Items using pointers

class AddressClass
{
  public:
    int      Code;
    char[50] Street;
    char[10] Number;
    char[50] POBox;
    char[50] City;
    char[50] State;
    char[50] Country;
};

class CustomerClass
{
  public:
    int           Code;
    char[50]      FirstName;
    char[50]      LastName;
    // "Address" IS A pointer !!!
    AddressClass* Address;
};

.......................................
..+-----------------------------+......
..|        AddressClass         +<--+..
..+-----------------------------+...|..
..| [+] int:      Code          |...|..
..| [+] char[50]: Street        |...|..
..| [+] char[10]: Number        |...|..
..| [+] char[50]: POBox         |...|..
..| [+] char[50]: City          |...|..
..| [+] char[50]: State         |...|..
..| [+] char[50]: Country       |...|..
..+-----------------------------+...|..
....................................|..
..+-----------------------------+...|..
..|         CustomerClass       |...|..
..+-----------------------------+...|..
..| [+] int:      Code          |...|..
..| [+] char[50]: FirstName     |...|..
..| [+] char[50]: LastName      |...|..
..| [+] AddressClass*: Address  +---+..
..+-----------------------------+......
.......................................

int main(...)
{
   CustomerClass* MyCustomer = new CustomerClass();
     MyCustomer->Code = 1;
     strcpy(MyCustomer->FirstName, "John");
     strcpy(MyCustomer->LastName, "Doe");

     AddressClass* MyCustomer->Address = new AddressClass();
     MyCustomer->Address->Code = 2;
     strcpy(MyCustomer->Address->Street, "Blue River");
     strcpy(MyCustomer->Address->Number, "2231 A");
   
     free MyCustomer->Address();
     free MyCustomer();

   return 0;
} // int main (...)

If you check the difference between both ways, you'll see, that in the first technique, the address item is allocated within the customer, while the second way, you have to create each address explictly.

Warning: Java allocates objects in memory like this second technique, but, the syntax is like the first way, which may be confusing to newcomers to "C++".

Implementation

So your list example could be something similar to the following example.

class Node
{
  public:
   Node(int data);

   int m_data;
   Node *m_next;
};

.......................................
..+-----------------------------+......
..|            Node             |......
..+-----------------------------+......
..| [+] int:           m_data   |......
..| [+] Node*:         m_next   +---+..
..+-----------------------------+...|..
....................................|..
..+-----------------------------+...|..
..|            Node             +<--+..
..+-----------------------------+......
..| [+] int:           m_data   |......
..| [+] Node*:         m_next   +---+..
..+-----------------------------+...|..
....................................|..
..+-----------------------------+...|..
..|            Node             +<--+..
..+-----------------------------+......
..| [+] int:           m_data   |......
..| [+] Node*:         m_next   +---+..
..+-----------------------------+...|..
....................................v..
...................................[X].
.......................................

Summary

Since a Linked List has a variable quantity of items, memory is allocated as is required, and, as is available.

UPDATE:

Also worth to mention, as @haccks commented in his post.

That sometimes, references or object pointers, indicate nested items (a.k.a. "U.M.L. Composition").

And sometimes, references or object pointers, indicates external items (a.k.a. "U.M.L. Aggregation").

But, nested items of the same class, cannot be applied with the "no-pointer" technique.

Solution 8 - C++

On a side note, if the very first member of a class or struct is the next pointer (so no virtual functions or any other feature of a class that would mean next isn't the first member of a class or struct), then you can use a "base" class or structure with just a next pointer, and use common code for basic linked list operations like append, insert before, retrieve from front, ... . This is because C / C++ guarantees that the address of the first member of a class or structure is the same as the address of the class or structure. The base node class or struct would only have a next pointer to be used by the basic linked list functions, then typecasting would be used as needed to convert between the base node type and the "derived" node types. Side note - in C++, if the base node class only has a next pointer, then I assume that derived classes can't have virtual functions.

Solution 9 - C++

>Why is it better to use pointers in a linked list?

The reason is that when you create a Node object, compiler has to allocate memory for that object and for that the size of object is calculated.
Size of pointer to any type is known to compiler and therefore with self referential pointer size of object can be calculated.

If Node m_node is used instead then compiler has no idea about the size of Node and it will stuck in an infinite recursion of calculating sizeof(Node). Always remember: a class cannot contain a member of its own type.

Solution 10 - C++

Because this in C++

int main (..)
{
    MyClass myObject;

    // or

    MyClass * myObjectPointer = new MyClass();

    ..
}

is equivalent to this in Java

public static void main (..)
{
    MyClass myObjectReference = new MyClass();
}

where both of them create a new object of MyClass using the default constructor.

Solution 11 - C++

> Why do linked lists use pointers instead of storing nodes inside of > nodes?

There is of course a trivial answer.

If they didn't link one node to the next by a pointer, they wouldn't be linked lists.

The existence of linked lists as a thing is because we want to be able to chain objects together. For example: we already have an object from somewhere. We now want to put that actual object (not a copy) at the end of a queue, for example. That is achieved by adding a link from the last element already on the queue to the entry we are adding. In machine terms, that's filling in a word with the address of the next element.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionm0meniView Question on Stackoverflow
Solution 1 - C++emlaiView Answer on Stackoverflow
Solution 2 - C++pm100View Answer on Stackoverflow
Solution 3 - C++cmaster - reinstate monicaView Answer on Stackoverflow
Solution 4 - C++NawazView Answer on Stackoverflow
Solution 5 - C++wallykView Answer on Stackoverflow
Solution 6 - C++Brent BradburnView Answer on Stackoverflow
Solution 7 - C++umlcatView Answer on Stackoverflow
Solution 8 - C++rcgldrView Answer on Stackoverflow
Solution 9 - C++haccksView Answer on Stackoverflow
Solution 10 - C++Khaled.KView Answer on Stackoverflow
Solution 11 - C++user13784117View Answer on Stackoverflow