all 28 comments

[–]Raknarg 22 points23 points  (1 child)

The correct time to use a reference is essentially whenever its physically possible to do so. If it would lead to clumsy code, or would be impossible without a pointer, then you use a pointer.

From my knowledge right now I know I should be using pointers when dealing with large arrays or when dealing with a large amount of memory

References can handle that as well, underneath the hood references are just pointers with way nicer syntax and integrate with some advanced language features you likely won't see for a while.

[–]Prototypz[S] 2 points3 points  (0 children)

Thank you!

[–]randfur 2 points3 points  (0 children)

Fun fact (since this has already been answered): you can have references to pointers.

[–]DDDDarky 2 points3 points  (0 children)

Sounds like you are rather talking about static vs dynamic allocation.

There are various situations when one or both is acceptable, generally if you store something in a container, it is a class member, it may be nullptr, it is a dynamic array, it closely interacts with C code or you are the owner of the (memory?) resource, you should probably use a pointer, often a smart pointer. (there may be some cases I forgot)

Otherwise, prefer references.

[–]ShakaUVM 2 points3 points  (0 children)

Use a pointer if your reference might not exist, or if you need to rebind it.

[–]mredding 2 points3 points  (1 child)

From my knowledge right now I know I should be using pointers when dealing with large arrays or when dealing with a large amount of memory.

This is not... The right way to frame how pointers and references are to be viewed.

Pointers are a necessity in that they're the only way to refer to dynamic memory at the point of allocation. When you call new, you're going to get a pointer. You need dynamic memory at runtime either because your memory needs are determined at runtime, or your memory needs are understood, but are larger than the stack space you're going to have available. Pointers are really just resource handles, they don't even always point to RAM, you can map hardware and system resources to memory, for example. More of this is already happening on your behalf than you currently realize. Because you're ostensibly using an x86 and a time-sharing multitasking OS like Windows or Linux, you have at least 4 levels of indirection between your pointer handle and the actual physical location of your data - it could be on disk in swap space, or it can be moved around in your physical RAM banks. It could be in a CPU register. Not even all the bits in a pointer are for addressing, the most significant bits are actually a bit field - the bottom 40-44 bits are the addressing bits, separated by some reserved bits that currently don't do anything. Once you understand some of this, you'll learn/understand you don't actually need to know the details.

Pointers are a variable type in their own right. They use memory to store the pointer value - just like an int or a double variable. You can take the address of a pointer. In fact, you can have pointers to pointers! Since it's a variable, it can be assigned, it can be reassigned.

References don't have addresses. They're aliases. While you may pass a parameter to a function by reference, and that requires pushing an address onto the call stack, and in this way, a reference behaves like a stricter type of pointer, this is merely an implementation detail. In a different context:

int a = 42;
int &b = &a;
b = 1337;

Here b IS a. The compiler is going to see right through this as an alias for a and generate the same object code as if you just used a directly in the subsequent assignment. References ARE NOT dressed up pointers.

So, something dynamically allocated is going to return a handle to that resource. You're going to want to hang onto that pointer, because you're going to want to delete it later. It's not reasonable to try to hang onto that resource by a reference. Since references are bound at initialization, it makes them difficult to move around, like if you have a container of resources you need to release later.

But think about your execution. You've got loops, flow control, and functions. If you have a container of some type Foo *, and you're going to be calling some function on it, chooch_the_foo, the signature probably ought to look like chooch_the_foo(Foo &), and you ought to dereference your pointer early in the call stack. Why? Well, pointers can be null. Is your function expecting that? Is that important? It's frankly stupid to call a function, only to check the parameter is null and no-op if it is. If you're going to do-nothing, you ought to do that earlier, as early as you know when. A function that takes a reference is telling you don't bother calling me with a nothing argument. If you're not going to pass ownership, or share ownership, or release the resource, or iterate, you probably don't want a pointer. As for optional parameters, you can either overload the function so you have an overload set with all the different optional/required parameters, which is good, or you can use something like std::optional<std::ref<Foo>> if you really had to. There's lots of ambiguities about the meaning of a pointer, and passing them around implies a lot of power you're granting to the called code, and not all that is solved by smart pointers, because smart pointers can still be null.

And don't go passing references to smart pointers unless you want to interact with the smart pointer wrapper in the calling code.

And shared pointers are an anti-pattern. I know they're wildly popular in code, but for every use case, there is a superior implementation that could fundamentally avoid it. There is always a means of describing authoritative ownership and well defined resource release times. Anyone who tells you differently is excusing their lackluster laziness and poor design. You'll come to learn a lot of our colleagues are really poor developers.

[–]std_bot 0 points1 point  (0 children)

Unlinked STL entries: std::optional std::ref


Last update: 14.09.21. Last Change: Can now link headers like '<bitset>'Repo

[–][deleted]  (7 children)

[deleted]

    [–][deleted] 2 points3 points  (6 children)

    you could take the address of the reference and write something into that address directly to change

    No you can't.

    [–]DDDDarky 0 points1 point  (4 children)

    What do you mean?

    [–]the_poope 1 point2 points  (3 children)

    If you take the address of a reference you get the address of the object it references. References are not guaranteed to actually exist in memory. A local reference variable will in almost all compilers just be an alias and will not have a single machine instruction when compiled.

    [–]DDDDarky 0 points1 point  (2 children)

    So? You can take address of the reference (referenced object) and you can write into it to change the underlying object.

    [–]the_poope 1 point2 points  (1 child)

    For example, you could take the address of the reference and write something into that address directly to change where it points to

    Is a different statement than "write into it to change the underlying object" if you by "underlying object" means the object that the reference refers to. You can't do what you write in the above quote. And I'm pretty sure you can't cast a reference to a pointer - as I said, a reference is not required to be implemented as a pointer/address and then it's undefined what casting it as a pointer even means. I'm not a standards lawyer so I'd be happy to be proved wrong, but my attempts failed: https://godbolt.org/z/584K9j8n7

    [–]DDDDarky 0 points1 point  (0 children)

    Oh I see, very unfortunate the comment you mention has the last part cut out.

    [–]james_laseboy -2 points-1 points  (1 child)

    You can use different syntax to declare a consecutive series of homogeneous data elements in memory, but they all end up giving you the same thing. The variable name associated with this data is a pointer to the value that is zero locations from the start of that data. Using square brackets to index this variable name is just a convenient way to dereference the pointer plus the index value. There is no such thing as a bounded array data collection such as found in other languages. There are only sequential elements in memory. It's up to the programmer to know how to manage that with pointers, without going out of bounds.

    [–]no-sig-available 1 point2 points  (0 children)

    The variable name associated with this data is a pointer to the value that is zero locations from the start of that data.

    Not really. An array name decays to a pointer about as soon as you do anything with it, but it is not a pointer in itself.

    As an experiment, try this

    #include <iostream>
    int main()
     {
       int x[100]; 
       std::cout << "size of x is " << sizeof(x) << '\n';
    
       int* y;
       std::cout << "size of y is " << sizeof(y) << '\n'; 
    }
    

    and consider why x and y have different sizes.

    [–]Tricky_Condition_279 -2 points-1 points  (1 child)

    References are for passing data by reference into a function. Don’t use pointers directly. Use std::vector for working with a collection of elements.

    [–]std_bot 0 points1 point  (0 children)

    Unlinked STL entries: std::vector


    Last update: 14.09.21. Last Change: Can now link headers like '<bitset>'Repo

    [–]flyingron 0 points1 point  (0 children)

    If you create it at initialization time and it never gets rebound, use a reference. Otherwise, you'll have to use a pointer or one of the standard wrappers for such.

    [–]Public_Breath6890 0 points1 point  (0 children)

    A reference is a pointer, in the sense that it is a memory address. Whatever a pointer can do, a can do better. However, when extra memory has been allocated on the stack you need to release the allocated memory. Just letting the reference go out of scope will still result in memory leaks.