Pointers and References
Pointers and References
Understand addresses, indirection, aliasing, and when references are safer than raw pointers.
Pointers and References
Understand addresses, indirection, aliasing, and when references are safer than raw pointers.
int value = 10;
int& ref = value;
ref = 20;
Changing ref changes value because they refer to the same object.
int value = 10;
int* ptr = &value;
*ptr = 20;
A pointer stores an address. Dereferencing with *ptr means "follow that address to reach the object".
Use nullptr, not NULL or 0.
int* ptr = nullptr;
void rename(std::string& name);
void print(const std::string& name);
void maybe_use(const Widget* widget);
void take_owner(std::unique_ptr<Widget> widget);
These four signatures communicate four different contracts: mutate required object, read required object, observe optional object, and transfer ownership.
void increment(int& x) {
++x;
}
void increment_if_present(int* x) {
if (x) {
++*x;
}
}
The reference version says the caller must provide a valid object. The pointer version says absence may be allowed and must be handled.
const std::string& bad_name() {
return std::string{"temporary"};
}
This returns a reference to an object that is destroyed immediately, which makes later use undefined behavior.
std::vector<int> values{1, 2, 3};
int* first = &values[0];
values.push_back(4);
If the vector reallocates, first may no longer point to a valid element. This is one of the most common real-world pointer bugs in ordinary container code.
Widget* widget = new Widget();
delete widget;
// widget is now dangling
Once an object is deleted, any remaining pointer value is only useful as a thing to overwrite or set to nullptr. It is no longer a safe handle to the object.
Pointers and references often mean "I can access this object", not "I own it". In modern C++, ownership should usually be represented by:
std::unique_ptrstd::shared_ptr when shared ownership is truly requiredIf you only need to read or mutate an object during one call, use a reference, pointer, std::span, or std::string_view instead of reaching for a smart pointer.
References are safer than pointers for required inputs because they cannot be null and they make that guarantee part of the function signature.
const correctnessvoid print(const std::string& text);
void mutate(std::string& text);
When you add const accurately, both humans and the compiler get a stronger picture of what a function may change.
Modern C++ often replaces raw pointer-and-length pairs with std::span<T> and raw C strings with std::string_view.
void process(std::span<const int> values);
void greet(std::string_view name);
These types still do not own the data, but they express the shape of the borrowed input more clearly than raw pointers alone.
std::spanstd::string_viewvoid set_title(std::string& text);
void maybe_log(const Logger* logger);
void take_buffer(std::unique_ptr<Buffer> buffer);
Read these as contracts: required mutable object, optional observer, and ownership transfer.
int main() {
int value = 10;
int* ptr = &value;
int& ref = value;
*ptr += 1;
ref += 1;
return value;
}
Pass the same object through both a pointer and a reference parameter. That makes the nullable-versus-required distinction concrete.
void increment_ptr(int* value) {
if (value) {
++*value;
}
}
void increment_ref(int& value) {
++value;
}