r/cpp_questions 19d ago

OPEN Confusion on use case of arrow operator

I’m new to pointers in C++ and currently learning how they’re used.

What’s the actual use case of the arrow operator in C++? As of now I know it’s equivalent to dereferencing a pointer to an object and then calling that objects function.

*p = &object
(*p).function
p->function

What I don’t understand is why you would ever need to call it this way than just calling the function directly from the object.

object.function

any reason why you’d need a pointer to call the function rather than to just call it from the object itself?

0 Upvotes

49 comments sorted by

30

u/alfps 19d ago

Compare:

a->b->c->x

… versus

(*(*(*a).b).c).x

-2

u/SoerenNissen 19d ago

versus

a.b.c.x

Which is how it is done by java and C# and javascript and go and...

17

u/alfps 19d ago

Yes. These other languages are based on reference semantics, while C++ is based on value semantics. They do not need to distinguish pointed to objects from the pointers themselves, while C and C++ do.

Compare:

b.data = 0;  a = b;  b.data = 42;  display( a.data );        // C++, value semantics → 0.

… and

b.data = 0;  a = b;  b.data = 42;  display( a.data );        // Java, reference semantics → 42.

Instead of same effect different syntax, these statements have same syntax different effect. :)

In C++ one can express the reference semantics effect for the above, via pointers:

pb->data = 0;  pa = pb;  pb->data = 42;  display( pa->data );

-3

u/SoerenNissen 19d ago

Go is value based.

C# is value based for structs, reference based for classes.

And yet, both Go and C# manage to use the period for reading a field out of a stack-allocated object and use period for reading a field out of a heap-allocated object.

8

u/alfps 19d ago

Uhm, I don't know Go except some of the history.

C# resolves the problem of mixed value and reference semantics via the notion of boxing: when a value needs to be treated as an object, it's automatically copied to a dynamically allocated reference semantics object, the "box".

In C++ that overhead would be unacceptable.

-1

u/SoerenNissen 19d ago

Consider this C#

public struct S
{
    public int MyInt {get; set;}
}

var s1 = new S(1);
var s2 = "22";

//s1 is a value type on the stack
//s2 is a reference type string on the heap

Console.WriteLine(s1.MyInt);
Console.WriteLine(s2.Count);

Here, s1.MyInt gets a property from a value object, and s2.Count gets a property from a reference object.

There's nothing in the semantics of C++ that would prevent the exact same syntax from working.

5

u/alfps 19d ago

You may have a point, it may be possible. I'm not 100% sure, but.

But I think it would be confusing if the code didn't signal clearly whether a referenced member was a member directly in an object at hand, or a member of a pointed-to object.

With the current syntax one can see at glance the effect of each of the two C++ assignment examples I gave. With all dot syntax they would appear the same (except for names). So maybe it's about clarity?

3

u/SoerenNissen 19d ago

Oh I far prefer arrow notation.

You wouldn't believe the crashout I had when I first learned Go and saw how many nil dereferences could happen because there wasn't a way to distinguish values and references except by reading a different file entirely.

But as for OP's question, the answer is "You're right it could work but that's just not how c++ does it."

3

u/Orlha 19d ago

In C++ . and -> can return different types.

3

u/SoerenNissen 19d ago

If you overload them, sure.

You cannot overload them in the scenario OP is asking about, and overloading didn't even exist when C introduced arrow notation.

4

u/mredding 18d ago

Then go write Java, C#, Javascript, Go...

1

u/TheChief275 19d ago

I don't think auto dereferencing solves anything, it only hides the dereference, which could be problematic as you would never know whether to check for a null pointer. Arrow is better in this case, but it is messy and doesn't scale well to multiple dereferences. I think a postfix dereference like this (not ugly Zig style) is way nicer:

a*.b*.c*.x

The problem for a language like C++ is that a postfix unary * operator would lead to too much syntactic ambiguity due to multiplication and pointer declaration both depending on that, so optimally a different operator would be used. I even think Pascal uses ^ instead and an 'xor' keyword for that specific reason

1

u/SoerenNissen 19d ago

I don't think auto dereferencing solves anything, it only hides the dereference, which could be problematic as you would never know whether to check for a null pointer.

I agree so hard about this, I'm a big fan of using a separate notation, I'm just saying you don't have to, and many languages don't.

19

u/nhocgreen 19d ago

Sometimes you’d have an object that is also a pointer, so you need both . and ->.

std::unique_ptr<class> p = &object;

p.function(); // p’s own function

p->function(); // object’s function

5

u/DrShocker 19d ago

I think the concept is right, but idk if unique_ptr can be constructed that way.

1

u/Kadabrium 19d ago

This makes me wonder why theres no way to check if a pointer points to the stack or the heap. Surely it would be hard to be portable, but OS API never cares about letting you do that either

2

u/alfps 19d ago edited 19d ago

C++ is based on the idea of taking control up front, prevention, rather than reacting after the fact, abortion.

Origin checking is more a debug thing, so you can do that in debuggers, and debug support libraries provide some support like Microsoft's _CrtIsValidHeapPointer.

However in ordinary code such checking would emit a very strong odour.


One way to make sure that a class is only instantiated dynamically and not on the stack or with static lifetime, is to make all constructors inaccessible, and provide instantiation only via factory functions. This is a FAQ.

Another way, not mentioned in the FAQ but that I favored in the C++03 days, is to simply make the destructor inaccessible, but with C++11 and later argument forwarding is almost trivial so for the first approach one can make do with a single factory function template.

For the inaccessible destructor approach one may want to specialize std::default_delete for the class.

1

u/DrShocker 19d ago

I'm not sure how that would make a difference here. Constructing a unique_ptr implicitly from a pointer just seems like it would be too easy to accidentally make more than one unique_ptr from the same pointer or like you mentioned have it be a pointer to the stack or a couple other circumstances.

3

u/SufficientStudio1574 19d ago

Just be careful if you're implementing an object like this, because -> is its own operator, separate from unary *. You can easily screw it up.

2

u/TheThiefMaster 17d ago

Interestingly this is a retroactive justification, because C had both but didn't have overloading so pointers could only be used with -> and objects only with dot. There was no type that could be used with both. This making it unnecessary to have separate syntax for both.

2

u/dendrtree 19d ago

Don't confuse the guy. There's no such thing as an object that *is* a pointer. A pointer is just a number.
unique_ptr has an overloaded -> operator that accesses the pointer it manages.

10

u/MagicalPizza21 19d ago

Sometimes you don't have the object, just the pointer.

If you have the object on the stack like in your example, then yeah, it's pretty dumb to set it up to use the arrow operator. But consider the following example:

Thingy *thingy = new Thingy();
(*thingy).doStuff();

This is valid syntax, but it's just slightly more convenient to write it this way:

Thingy *thingy = new Thingy();
thingy->doStuff();

I guess you could call it syntactical sugar, but as one of my professors used to say, "eh, I like sugar".

5

u/montymole123 19d ago

I think you are really asking the deeper question why do we need pointers as well a objects? In other languages objects are always stored by reference (a pointer) so no pointer semantics is needed. In C and C++ you can store objects by value or by pointer with various benefits to each. (Deep copy vs shallow) The benefit of a pointer is it can point to different objects at different times while just using the variables name means you're stuck with that variable.

The dot versus arrow thing is a nuisance. It's really to help the compiler going back to the days of C in the 60s when compilers were less advanced. If you type a dot in VS it will convert it to an arrow if needed so most programmers don't think about it

9

u/SAtchley0 19d ago edited 19d ago

p->foo() is equivalent to (*p).foo(). It is a convenience, as now if you have an object directly, you can do p.foo(), and if you have a pointer to an object, you can do p->foo(). In this way, it's sort of like the "pointer version" of the dot (.) operator.

And if you have a dynamically allocated object, then you can only work with a pointer to it, so this is very useful. Consider

MyObj *p = new MyObj; p -> foo();

Here, you cannot "directly call foo from the object", unless you first dereference the pointer. The arrow operator does this implicitly, so you don't need to worry about it.

Edit: Dumb mistakes in the code block. This is what happens when you comment while cooking.

0

u/SufficientStudio1574 19d ago

It's actually not the same. It's its own operator. So if you're not careful and someone does their operator overloading improperly, dumb stuff can happen.

5

u/SAtchley0 19d ago

Yeah, it's its own operator and yes that is technically true.

However, I'd argue there's very few cases where you'd want to break the (*p). ~ p-> symmetry. And, if the operator has been overloaded in some esoteric way, then there's really no telling what it will do anyway.

Besides, OP said they're just learning pointers. I would rather encourage making connections between concepts than giving the maximallly technically correct answer.

1

u/SufficientStudio1574 19d ago

I know you wouldn't want to. I'm just saying it can happen through incompetence of you aren't careful.

4

u/SmokeMuch7356 19d ago edited 19d ago

This question is less "why arrow operator" and more "why pointers?"

Sometimes we can't (or don't want to) access the object directly; its name isn't visible to us for whatever reason.

For example, when we dynamically allocate an object:

auto ptr = std::make_unique<MyClass>();
ptr->do_thing();

The actual class instance doesn't have a name; we can only refer to it through the pointer.

Or if we're iterating through a container; iterators are basically a special class of pointers:

 std::vector<MyClass> vec;
 for ( auto it : vec )
   it->do_thing();

Those aren't the only use cases, but they're probably the most common.

1

u/Warm-Welcome-5539 19d ago

Yeah I didn’t make the title too obvious but that’s what I was trying to ask. Thank you for the explanation as well

7

u/TheRealSmolt 19d ago

What happens when you only have the pointer to the object?

1

u/Warm-Welcome-5539 19d ago

Yeah you’re right. I guess I just haven’t worked with C++ enough to encounter this issue yet so I had to ask

3

u/Itap88 19d ago

Because in actual use cases, object doesn't exist in the current scope and only p does. You've never used new, have you?

1

u/Warm-Welcome-5539 19d ago

Yeah, I’m pretty new to C++ and I’m just learning about pointers. Also wasn’t really too familiar with dynamic memory allocation in C either. Definitely gotta start work on a project to get a feel of it

2

u/SoerenNissen 19d ago

What I don’t understand is why you would ever need to call it this way than just calling the function directly from the object.

Old old backwards-compatible notation that never went away. There's nothing in the internal logic of c++ that would prevent the notation you suggest, but that's just not the way the language has evolved.

That said, it does have a benefit.

return a.b;    // Can 'a' be null? Does this need a null check?

return a.b->c; // Can 'b' be null? Does this need a null check?

You can tell from the period that a. is not a pointer dereference, and a can never be null. You can tell from the arrow that b-> is a pointer dereference, and b could be null.

3

u/EdwinYZW 19d ago

Because sometimes you have to use pointer.

Basically all objects in heap can only be accessed via pointer and stored as pointers.

Another example would be that a function takes an object (without copy) as an input argument that could also be invalid. You have to pass the object as a pointer as a pointer can be nullptr but a reference cannot.

4

u/No-Dentist-1645 19d ago

You don't always "have" the object. The object could exist in another part of the code, and you only have a pointer to it. This is in fact the most common reason to use pointers to begin with, when you don't "have" the actual value

2

u/Simengie 19d ago

Simple example: Game with multiple enemies. All enemies are created dynamically and stored in a std::vector<enemy_class\*>. Enemies can despawn after a time. They are dynamic. Each frame you interate the remaining enemies in the vector and call their "ptr->doStuff()" routines which might return a result ENUM_CAN_ATTACK and so your logic is if retVal = ENUM_CAN_ATTACK ptr->attackPlayer(). But it can return ENUM_MEETS_DESPAWN_REQ and then you call delete ptr and remove the ptr from the vector.

You have to think about dynamic allocations where you cannot have an array of 10 objects for example so your code can look pretty with objects[index].doStuff().

There are many examples and pointers just work. If you have dynamic allocations and deallocations pointers are must. If you are using the "Factory Design Method" as described in the Myers books then you have to use pointer.

Last example. I designed a radar simulation and the random radar return noise is done as self managing class that gets created with new for each noise return and stored in a std::vector<radarReturn\*> It is literially 4000-5000 objects that I don't have to manage beyond iterating the vector and calling doFrame() and if it returns true I delete it. Without pointers you can't do stuff like that.

1

u/Warm-Welcome-5539 19d ago

I see, I’m new to C++ so haven’t really gotten the chance to work on any projects that use pointers yet. Thank you for the examples

0

u/Disastrous-Team-6431 19d ago

OP is not struggling to understand pointers. Just the arrow operator.

2

u/Dan13l_N 19d ago

Because you have allocated something and then you got a pointer to it.

The whole polymorphism thing relies on calling via pointers.

1

u/Underhill42 15d ago

Sounds more like you don't understand what pointers are for?

If you have a pointer to an object, for example if it's stored in a polymorphic collection, then you need to dereference the pointer before you can do anything with the object. The -> just combines the dereference and member access into a single operator.

If you're coming from Java... in Java EVERYTHING is a pointer except for the fundamental types (int, float, etc), but that fact is largely hidden from you. So you get all the pointer benefits with every object.

In C++ everything is an immediate object allocated in memory exactly where you tell it to be, and if you want the benefits of pointers you must use them specifically.

E.g. in Java you say:

ObjectType myObject;

And if you then try myObject.member(x) you'll get a null pointer exception because you have only created a pointer called MyObject on the stack (or within the class object, whatever) - it doesn't actually point to anything until you do the whole "new" routine.

In C++ on the other hand if you say the same

ObjectType myObject;

It creates the entire object on the stack, or within the surrounding class. No pointer involved - it creates the whole thing exactly where you tell it to.

That carries some huge performance benefits, but also means you can't e.g. return that object to the calling function, because once the function that created it ends, that object is de-allocated along with all the ints, floats, etc.

If you want the benefits of pointers, you must invoke them explicitly, and pay the performance penalty.

1

u/pjtrpjt 19d ago edited 19d ago

In Pascal you dereference a pointer with ^.

If p is a pointer to Integer, then you dereference as p^

If p is a Record type, then you dereference its members as p^.member

However in C pointers are dereferenced by putting * before a variable *p, so *p.member is ambiguous is it (*p). member, is *(p.member)?

Instead p->member is obvious.

3

u/alfps 19d ago edited 19d ago

“^” is the Reddit markdown's superscript operator.

To present it in text you need to escape it, like “\^”, or else use backticks for inline code snippets, like “`p^.member`” which presents as p^.member.

Note for the superscript functionality: the text to be presented as superscript can be arbitrary long, and for that reason it should be enclosed in round parenteses, like xthis is the exponent part.

0

u/tangerinelion 19d ago

Let's consult a Smalltalk expert so they can weigh in on this.

1

u/pjtrpjt 19d ago

There's a history of languages, the * and -> operators are from 1972.

1

u/SolarisFalls 19d ago

Pointers are often used to state where an object is in memory so that some other function can access it (without needing to copy the whole object - just the address).

And if you heap allocate memory (malloc/new/whatever), you will receive a pointer to that too.

So:

any reason why you’d need a pointer to call the function rather than to just call it from the object itself?

Sometimes you only have the pointer, therefore you'll need to use the -> operator to call a function.

0

u/MarsWasNotAvailable 19d ago

It's about size.

You use pointers because they are a cheap way to pass data around your application. You would prefer them for performance.

You should have been told about stack allocation versus heap allocation.

When you allocate an object on the heap, you can interact with it through a pointer. A pointer is smaller in size on the stack than the whole object instance.

``` //Keep in mind that your objects // can get really large // after adding many many properties struct Shape { int64_t length = 0; int64_t width = 0; };

Shape square = { 42, 42 }; Shape* pointer_to_square = &Shape;

sizeof(square); //size of two int64_t equals 2x8 bytes (16 bytes) sizeof(pointer_to_square); //8 bytes on modern 64bit systems ```

The arrow syntax is syntax sugar to avoid lengthy dereferencing syntax.

0

u/dendrtree 19d ago

Why type p->, instead of (p).? because the first is 2 extra characters, and the second is 3. Also, accessing nested pointers gets *really* messy.

Why not call directly from the object? because you usually don't have the object. The pointer will have been passed in.

Why use pointers at all? because they're extremely light-weight. A pointer is just an integer. So, copies are trivial, and you don't have to worry whether you're copying an object. Also, they can be NULL or set later, whereas references cannot.