Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Accessing Members Indirectly
Access struct members through pointers using the arrow operator (->).
Member selection with pointers and references
In the Structs lesson, we showed that you can use the member selection operator (.) to select a member from a struct object:
#include <iostream>
struct Sensor
{
int id {};
double temperature {};
int humidity {};
};
int main()
{
Sensor basement { 301, 18.5, 65 };
// Use member selection operator (.) to select a member from struct object
++basement.humidity; // humidity increased
basement.temperature = 19.0; // temperature rose
return 0;
}
Since references to an object act just like the object itself, we can also use the member selection operator (.) to select a member from a reference to a struct:
#include <iostream>
struct Sensor
{
int id {};
double temperature {};
int humidity {};
};
void printSensor(const Sensor& s)
{
// Use member selection operator (.) to select member from reference to struct
std::cout << "Id: " << s.id << '\n';
std::cout << "Temp: " << s.temperature << '\n';
std::cout << "Humidity: " << s.humidity << '\n';
}
int main()
{
Sensor basement { 301, 18.5, 65 };
++basement.humidity;
basement.temperature = 19.0;
printSensor(basement);
return 0;
}
Member selection for pointers to structs
However, the member selection operator (.) can't be used directly on a pointer to a struct:
#include <iostream>
struct Sensor
{
int id {};
double temperature {};
int humidity {};
};
int main()
{
Sensor basement { 301, 18.5, 65 };
++basement.humidity;
basement.temperature = 19.0;
Sensor* ptr { &basement };
std::cout << ptr.id << '\n'; // Compile error: can't use operator. with pointers
return 0;
}
With normal variables or references, we can access objects directly. However, because pointers hold addresses, we first need to dereference the pointer to get the object before we can do anything with it. So one way to access a member from a pointer to a struct is as follows:
#include <iostream>
struct Sensor
{
int id {};
double temperature {};
int humidity {};
};
int main()
{
Sensor basement { 301, 18.5, 65 };
++basement.humidity;
basement.temperature = 19.0;
Sensor* ptr { &basement };
std::cout << (*ptr).id << '\n'; // Not great but works: First dereference ptr, then use member selection
return 0;
}
However, this is somewhat awkward, especially because we need to parenthesize the dereference operation so it takes precedence over the member selection operation.
To provide cleaner syntax, C++ offers a member selection from pointer operator (->) (also sometimes called the arrow operator) that can be used to select members from a pointer to an object:
#include <iostream>
struct Sensor
{
int id {};
double temperature {};
int humidity {};
};
int main()
{
Sensor basement { 301, 18.5, 65 };
++basement.humidity;
basement.temperature = 19.0;
Sensor* ptr { &basement };
std::cout << ptr->id << '\n'; // Better: use -> to select member from pointer to object
return 0;
}
This member selection from pointer operator (->) works identically to the member selection operator (.) but does an implicit dereference of the pointer object before selecting the member. Thus ptr->id is equivalent to (*ptr).id.
This arrow operator is not only easier to type, but is also much less prone to error because the indirection is implicitly done for you, so there are no precedence issues to worry about. Consequently, when doing member access through a pointer, always use the -> operator instead of the . operator.
When using a pointer to access a member, use the member selection from pointer operator (->) instead of the member selection operator (.).
If the member accessed via operator-> is a pointer to a class type, operator-> can be applied again in the same expression to access the member of that class type.
The following example illustrates this:
#include <iostream>
struct Location
{
double latitude {};
double longitude {};
};
struct Device
{
Location* position {};
Location* target {};
Location* destination {};
};
int main()
{
Location home { 40.7, -74.0 };
Location work { 40.8, -73.9 };
Location store { 40.6, -74.1 };
Device tracker { &home, &work, &store };
Device* ptr { &tracker };
// ptr is a pointer to a Device, which contains members that are pointers to a Location
// To access member longitude of Location destination of the Device pointed to by ptr, the following are equivalent:
// access via operator.
std::cout << (*(*ptr).destination).longitude << '\n'; // ugly!
// access via operator->
std::cout << ptr -> destination -> longitude << '\n'; // much nicer
}
When using more than one operator-> in sequence (e.g., ptr->destination->longitude), the expression can be hard to read. Adding whitespace between the members and operator-> (e.g., ptr -> destination -> longitude) can make it easier to distinguish the members being accessed from the operator.
Mixing pointers and non-pointers to members
The member selection operator is always applied to the currently selected variable. If you have a mix of pointers and normal member variables, you can see member selections where . and -> are both used in sequence:
#include <iostream>
#include <string>
struct Processor
{
int cores {};
};
struct Computer
{
std::string model {};
Processor cpu {};
};
int main()
{
Computer laptop { "ThinkBook", { 8 } };
Computer* ptr { &laptop };
// ptr is a pointer, use ->
// cpu is not a pointer, use .
std::cout << (ptr->cpu).cores << '\n';
return 0;
}
Note that in the case of (ptr->cpu).cores, parentheses aren't necessary since both operator-> and operator. evaluate in left to right order, but they do help readability slightly.
Summary
Member selection with references: References act like the object they reference, so use the normal member selection operator (.) with references to structs, just as you would with the struct object itself.
Member selection with pointers: The member selection operator (.) cannot be used directly on pointers to structs. You must either dereference the pointer first using (*ptr).member (requiring parentheses due to precedence), or use the member selection from pointer operator (->).
Arrow operator (->): The member selection from pointer operator (->) is specifically designed for accessing members through pointers. It combines dereferencing and member selection in one operation: ptr->member is equivalent to (*ptr).member but much cleaner.
Why prefer arrow operator: The arrow operator is easier to type, more readable, and less error-prone than the dereference-then-select approach. It handles the indirection implicitly, eliminating precedence concerns and potential errors.
Chaining arrow operators: When a member accessed via operator-> is itself a pointer to a class type, you can chain multiple -> operators together (e.g., ptr->destination->longitude). Adding whitespace (ptr -> destination -> longitude) can improve readability.
Mixing . and -> operators: When some members are pointers and others aren't, you'll use both operators in sequence. Use -> for pointer members and . for non-pointer members based on the currently selected variable's type.
Operator precedence: Both operator-> and operator. have the same precedence and associate left-to-right, so they evaluate in order from left to right. Parentheses can be added for clarity but aren't usually necessary.
Understanding when to use . versus -> is essential for working with pointers to structs and classes, particularly when dealing with dynamically allocated objects or data structures like linked lists and trees.
Accessing Members Indirectly - Quiz
Test your understanding of the lesson.
Practice Exercises
Database Record Access
Work with pointers to structs using the arrow operator. Implement functions that access struct members through pointers and references.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!