126

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.

m0meni
  • 16,006
  • 16
  • 82
  • 141
  • 2
    because a linked list is a list of pointers to a memory address. Java probably doesn't have real linked lists. – Ryan Apr 09 '15 at 16:20
  • 8
    Hint: what would the size of `Node` be? – Angew is no longer proud of SO Apr 09 '15 at 16:20
  • 14
    @self Pardon me? Why wouldn't a language where everthing is a pointer have no linked lists? – Angew is no longer proud of SO Apr 09 '15 at 16:21
  • 41
    It's important to note how C and C++ are distinct from Java in terms of object pointers vs references. `Node m_next` is not a reference to a node, it's storage for the entire `Node` itself. – Brian Cain Apr 09 '15 at 16:21
  • 3
    Java doesn't have pointers – Ryan Apr 09 '15 at 16:21
  • 41
    @self Java does have pointers you just don't explicitly use them. – m0meni Apr 09 '15 at 16:22
  • 27
    [Turtles all the way down](http://en.wikipedia.org/wiki/Turtles_all_the_way_down) is *not* an option. The madness has to end somewhere. – WhozCraig Apr 09 '15 at 16:22
  • 2
    @zenith technically you could use boost::optional, but really it's basically the same thing - a way for an object to have nullability and contain a reference to another object of the same type. – dwcanillas Apr 09 '15 at 16:24
  • 7
    @self: Java has object references, which have the same semantics as pointers in C and C++. – Mike Seymour Apr 09 '15 at 16:24
  • 4
    @dwcanillas: `boost::optional` and `std::optional` both reserve in the object an amount of space at least equal to `sizeof(T)`. For the reasons explained in zenith's answer, having `struct node { std::optional n; };` doesn't work. – Bill Lynch Apr 09 '15 at 16:28
  • 26
    Please forget **everything** you know about Java. C++ and Java handle memory in fundamentally different ways. Go see [this question for book recommendations](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list?lq=1) pick one, and read it. You'll be doing us all a huge favor. – Rob K Apr 09 '15 at 18:37
  • 1
    It is not the _only_ way ... You can just as easily use a preallocated array of structs with one of the members being a reference. If the array size is <=256, the reference can be a uint8_t (similar for uint{16,32}_t) which can be useful if the number of "nodes" is known to be under a certain threshold. The space savings on 64 bit is 256*7 (char), 65536*6 (short), etc ... because a pointer takes up 8 bytes. It is left as an exercise to each programmer when it makes sense to do this. – technosaurus Apr 10 '15 at 00:23
  • 1
    @technosaurus: But then what you have is not a linked list, it's an indexed array. – jamesqf Apr 10 '15 at 04:48
  • 3
    @jamesqf - No, it just uses an indexed array (basically as a pre-allocated memory pool), you can still operate on them as a linked list using the array index instead of a memory address (which, thanks to pointer math, can be represented by as little as 1 byte even for large structures as opposed to 8 bytes for a 64 bit address) It also simplifies reading from and writing to disk and can even use mmap. The downside is that extra code is needed to reuse "removed" nodes (though malloc isn't needed, so its an overall win) – technosaurus Apr 10 '15 at 05:46
  • 2
    @technosaurus - no, that's an array list, not linked list. Linked list is a specific implementation of list interface, and so is array list that you are describing. They both do the same thing, with different implementations. – Davor Apr 10 '15 at 13:22
  • 11
    @MikeSeymour - in Java spec, they are literally called pointers. Thats why you get `NullPointerException` in Java, not `NullReferenceException`. – Davor Apr 10 '15 at 13:24
  • @Davor: Thanks for the correction, I've always seen them referred to as "references", and never bothered to learn the official terminology. – Mike Seymour Apr 10 '15 at 14:33
  • In ancient times when memory was expensive and allocating it was expensive, we would pre-allocate memory for 100 nodes at a time say, and have to keep track of the "empties", so there may be ancestral memory lingering of "array"ish linked lists (in C). They were still linked lists because they were not contiguous. – mckenzm Apr 11 '15 at 05:17
  • @mckenzm This kind of "bucketed" linked list design is still quite common, an advantage is that keeping track of the "empties" can be done very efficiently with the right CPU instruction (keep them in a bitmap and use first-bit-set-like instructions) – Thomas Apr 11 '15 at 12:55
  • 7
    How is this not a duplicate more than 6 years after Stack Overflow launched? – Peter Mortensen Apr 12 '15 at 14:25
  • 2
    @Davor [Linked list](https://en.wikipedia.org/wiki/Linked_list) is type of data structure, [java.util.LinkedList](https://docs.oracle.com/javase/7/docs/api/java/util/LinkedList.html) is particular implementation of java.util.List - I believe technosaurus probably used the former meaning (with space) not latter. [And if we ignore fancy concepts like strict aliasing and virtual memory then all physical memory is just an array and pointers are index within that array so any linked list is implemented in such way]. – Maciej Piechotka Apr 12 '15 at 21:52
  • @PeterMortensen http://stackoverflow.com/questions/4004673/class-variable-within-its-definition Maybe? – Cole Tobin Apr 13 '15 at 02:56

11 Answers11

228

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.

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
  • 26
    *Unless it's evaluated lazily. Infinite lists are possible, just not with strict evaluation. – Carcigenicate Apr 09 '15 at 20:07
  • 1
    @svick It's actually Haskell that I was thinking of. Infinite lists are fun when the language allows them. I've never tried anything like that in Java though, so I can't speak to that. – Carcigenicate Apr 09 '15 at 21:16
  • 55
    @Carcigenicate it's not about evaluating/executing some function on the Node object - it's about the memory layout of every instance of Node, which must be determined at compile time, before any evaluation can occur. – Peteris Apr 09 '15 at 22:17
  • @Peteris If we didn't insist on knowing where the allocation of Node ends in virtual memory, as C and C++ do, we could perhaps make at least _one_ list storing one Node inside another. Allocating anything else in a higher address might be problematic, however. :-) But I don't know any language that works that way. – David K Apr 10 '15 at 05:43
  • 6
    @DavidK It's logically impossible to do this. You *need* a pointer (well really an indirection) here - sure the language can hide it from you, but in the end, no way around that. – Voo Apr 10 '15 at 07:32
  • 1
    @Voo Yes, it _is_ logically impossible, but the point made earlier (even before I commented) is that this is at least partly due to the design of the C and C++ languages. I find it amusing to contemplate an alternative language in which that data structure is possible. To repeat what I said before, however, in reality _I don't know any language that works that way._ – David K Apr 10 '15 at 12:38
  • 2
    @David I'm confused. First you agree that it is logically impossible, but then you want to contemplate it? Remove anything of C or C++ - it's impossible in *any* language you could ever dream up as far as I can see. That structure is by definition an infinite recursion and without some level of indirection we cannot break that. – Voo Apr 10 '15 at 13:08
  • 1
    @Voo The topic of the question is C++, and in that context this data structure is impossible. But there are many assumptions in C++ that we tend to carry over to other languages out of of habit rather than logical necessity. See the first comment above: "Unless it's evaluated lazily." That's a concept that's completely foreign to how the C language taught us to think about data structures; but that doesn't imply it's logically impossible in _every_ conceivable language. – David K Apr 10 '15 at 13:26
  • 1
    @DavidK Give me the mathematical definition of the structure then that doesn't have an infinite recursion in it. If you can do that, you've shown that it's at least theoretically possible to do so (if I have way too much free time tonight I might just prove this is impossible..). Lazy evaluation has nothing to do with this in any case (I've programmed in Haskell in the past, it's not like the concept is foreign to me) - you still allocate space for the thunk at creation (and that's where the indirection comes into place again). – Voo Apr 10 '15 at 13:53
  • @Voo A node is a tuple containing a value and `null` or a value and a node. Here's a recursive definition of a node with a stopping condition which is just fine. It's not only possible to define objects like this - it's also _extremely_ common, here's _actual (poor) code_ that runs in Haskell - `data List a = Node a (List a) | Nil`. – Benjamin Gruenbaum Apr 10 '15 at 14:40
  • 1
    Hint (it works because you _don't_ allocate space at creation, rather - it's lazy, because Haskell is a non-strict language that evaluates things only when it must. – Benjamin Gruenbaum Apr 10 '15 at 14:41
  • 13
    @benjamin I actually pointed out (because I knew otherwise someone would bring this up - well didn't help) that Haskell allocated the thunks at creation time and hence this works because those thunks give us the indirection we need. This is nothing but a pointer with extra data in disguise... – Voo Apr 10 '15 at 16:00
  • 1
    _Mathematically,_ the data structure _would_ have infinite recursion, just like one of the tapes of a Turing machine (which after all is one of the classic mathematical models of computing). Is it worthwhile to actually implement such a structure in practice? I doubt it. Is it possible to do so in a way that doesn't use pointers from one node to the next behind the scenes? Yes, but you might not like the resulting performance. – David K Apr 10 '15 at 17:17
  • 1
    @BenjaminGruenbaum You are describing a structure that includes pointers. Just because Haskell doesn't require you to explicitly call it a pointer doesn't mean that its not implemented as such. – Aaron Dufour Apr 10 '15 at 18:09
  • @Voo: In the example given, `Node` is mathematically equivalent to an infinite `int` array. You can implement an infinite array by reserving a suitably large section of address space, i.e., larger than the amount of memory you can actually commit, and having the OS commit memory as needed. The only constraint you have to place on the array in order to implement it like that is to insist that it cannot be sparse, which in this context is fine since the only way to reference element n+1 is via element n. You run out of resource before you run out of address space. – Harry Johnston Apr 11 '15 at 05:35
  • Alternatively, you can assume a processor architecture that allows both multiple address spaces (which is a real thing but is now out of fashion) and arbitrary address lengths (this has never existed as far as I know). :-) – Harry Johnston Apr 11 '15 at 05:43
  • 1
    @HarryJohnston But then every linked list is fixed size and since the last element has to be different, it's now not an infinitely recursive definition anymore. For the pointer case we'd need an arbitrary length encoding for the pointers to get around this but it'd be possible. – Voo Apr 11 '15 at 07:41
  • 1
    @Voo: I'm not sure what you mean by fixed size in this context, or why the last element has to be different. It's true that typically a linked list can be terminated by a null pointer rather than needing a separate mechanism to indicate where the end of the data is, but I'd file that objection under "well, if your declaration doesn't use links, then by definition it's not a linked list" rather than "your declaration is inherently impossible to implement". – Harry Johnston Apr 11 '15 at 07:51
  • 2
    @Voo, Haskell doesn't need to use thunks; that's just how GHC implements it. – PyRulez Apr 11 '15 at 18:16
178

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).

laike9m
  • 18,344
  • 20
  • 107
  • 140
pm100
  • 48,078
  • 23
  • 82
  • 145
  • 2
    @SalmanA I already knew this. I just wanted to know why it wouldn't work without a pointer which is what the accepted answer explained much better. – m0meni Apr 10 '15 at 11:25
  • 3
    @AR7 They're both giving kind of the same explanation, just under two different approaches. If you declared it as a "regular" variable, then the first time a constructor were called, it would instantiate that to a new instance. But before it finishes instantiating it - before the the first one's constructor is finished - the member `Node`'s own constructor would be called, which would instantiate another new instance...and you would get endless pseudo-recursion. It's not *really* so much a size issue in completely strict and literal terms, as it is a performance issue. – Panzercrisis Apr 10 '15 at 15:14
  • But all you're really wanting is just a way to point to whichever one's next in the list, not a `Node` that's actually inside the first `Node`. So you create a pointer, which is essentially how Java handles objects, as opposed to primitives. When you call a method or make a variable, Java doesn't store a copy of the object or even the object itself; it stores a reference to an object, which is essentially a pointer with a little bit of a kiddy glove wrapped around it. This is what both answers are essentially saying. – Panzercrisis Apr 10 '15 at 15:18
  • its not a size or speed issue - its an impossibility issue. The included Node object would include a Node object that would include a Node object... In fact it is impossible to compile it – pm100 Apr 10 '15 at 15:21
  • @pm100 That's what I mean. I'm not sure if the C++ specification would block it, but if a language were compiled so that the compiled code kept jumping back to the beginning of the compiled constructor recursively, it would be possible to compile; but it would be an infinite loop if the programmer didn't handle this properly in the constructor. – Panzercrisis Apr 10 '15 at 15:28
  • 3
    @Panzercrisis I'm aware that they're both giving the same explanation. This approach, however, wasn't as helpful to me because it focused on what I already had an understanding of: how pointers work in C++ and how pointers are handled in Java. The accepted answer addressed *specifically* why not using a pointer would be impossible because the size can't be calculated. On the other hand, this one left it more vaguely as " a recursively defined structure." P.S your explanation that you just wrote explains it better than both :D. – m0meni Apr 10 '15 at 15:42
  • IMHO Java's `Node m_node` is much more like C++'s `Node &m_node`, i.e. a reference rather than a pointer. – abligh Apr 13 '15 at 05:40
  • @abligh Why so? The Java `Node` can be null, whereas the C++ `Node&` can't. On the other hand, `Node*` could be null, which makes it identical to the Java `Node`. – Emil Laine Apr 15 '15 at 02:00
  • @zenith - it's not a perfect match, but consider e.g. `node1 = m_node``. In Java that code works similarly to C++ if `m_node` is defined as a reference, i.e. nothing is copied. – abligh Apr 15 '15 at 06:28
  • @abligh Actually, the value of the pointer (the memory address) *is* copied in both cases. – Emil Laine Apr 15 '15 at 14:55
38

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.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • 1
    To get something similar in Java would probably be "extends" if SuperNode extends Node, SuperNodes includes all Attributes of Node and has to reserve all the additional space. So in Java you cannot do "Node extends Node" – Falco Apr 13 '15 at 09:58
  • @Falco True, inheritance is a form of in-place inclusion of the base classes. However, since Java does not allow for multiple inheritance (unlike C++), you can only pull in an instance of a single other preexisting class via inheritance. That's why I wouldn't think of inheritance as a substitute for in-place member inclusion. – cmaster - reinstate monica Apr 13 '15 at 16:01
28

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.

Ben Lachman
  • 3,083
  • 1
  • 28
  • 44
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 5
    Worse, no valid size exists: If `k == sizeof(Node)` holds and your type has data, it would also have to hold that `sizeof(Node) = k + sizeof(Data) = sizeof(Node) + sizeof(Data)` and then `sizeof(Node) > sizeof(Node)`. – bitmask Apr 09 '15 at 16:27
  • 4
    @bitmask no valid size exists *in the real numbers*. If you allow transinfinites, `aleph_0` works. (Just being overly pedantic :-)) – k_g Apr 11 '15 at 06:02
  • 2
    @k_g Well, the C/C++ standard mandates that the return value of `sizeof` is an unsigned integral type, so there goes the hope of transfinite or even real sizes. (being even more pedantic! :p) – Thomas Apr 11 '15 at 12:59
  • @Thomas: One might even point out that there go even the Natural Numbers. (Going over the -pedantic top :p ) – bitmask Apr 12 '15 at 18:02
  • 1
    In fact, `Node` is not even defined before the end of this snippet, so you cannot really use it inside. Allowing one to implicitly forward-declare pointers to an as of yet undeclared class is a little cheat that is allowed by the language in order to make such structures possible, without the need to explicitly cast pointers all the time. – Sergey Orshanskiy Apr 13 '15 at 03:45
13

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.

wallyk
  • 56,922
  • 16
  • 83
  • 148
9

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();
}
Community
  • 1
  • 1
Brent Bradburn
  • 51,587
  • 17
  • 154
  • 173
  • The `Node_reference` declaration above addresses one of the most interesting language-level differences between Java and C++. In Java, declaring an object of type `Node` would implicitly use a reference to a separately allocated object. In C++, you have the choice of reference (pointer) vs. direct (stack) allocation -- so you have to handle the distinction explicitly. In most cases you would use direct allocation, although not for list elements. – Brent Bradburn Mar 21 '17 at 14:54
  • Don't know why I didn't also recommend the possibility of a [std::deque](https://en.cppreference.com/w/cpp/container/deque). – Brent Bradburn Apr 07 '20 at 04:33
8

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.

umlcat
  • 4,091
  • 3
  • 19
  • 29
7

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.

rcgldr
  • 27,407
  • 3
  • 36
  • 61
6

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.

Community
  • 1
  • 1
haccks
  • 104,019
  • 25
  • 176
  • 264
5

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.

Khaled.K
  • 5,828
  • 1
  • 33
  • 51
0

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.

user13784117
  • 1,124
  • 4
  • 4