- Explain what a pointer is, take an address with
&and dereference with* - Compare pointers with references and check for
nullptr - Explain how
newanddeleteallocate dynamic memory and why they are risky - Apply the RAII principle and
std::unique_ptr
You want to lend a friend a book, but it is very heavy. Instead of carrying it, you write the address on a slip of paper: “12 Nizami Street, shelf 3”. With the address, your friend can find the book alone. In C++ a pointer is exactly that slip of paper: it stores not the value itself, but its address in memory. Pointers give C++ speed and flexibility, but carelessness can be costly.
Addresses and pointers
A computer's memory consists of numbered cells, and every variable lives at some address. The & operator returns a variable's address. An address is usually printed in hexadecimal and may be different every time the program runs:
#include <iostream>
int main() {
int coins = 50;
std::cout << "Value: " << coins << '\n';
std::cout << "Address: " << &coins << '\n';
return 0;
}0x7ffc5a2b1c4c — it differs on every computer and every run.A variable that stores the memory address of another variable. int* p means “p is a pointer to an int”. The expression *p reaches the value stored at that address; this is called dereferencing.
#include <iostream>
int main() {
int coins = 50;
int* ptr = &coins;
std::cout << "coins = " << coins << '\n';
std::cout << "*ptr = " << *ptr << '\n';
*ptr = 75;
std::cout << "coins = " << coins << '\n';
std::cout << std::boolalpha << (ptr == &coins) << '\n';
return 0;
}coins = 50 *ptr = 50 coins = 75 true
*ptr = 75 changes the value at the pointer's address, that is, coins itself.| Syntax | Meaning |
|---|---|
&x | the address of x |
int* p | declares a pointer to an int |
*p | the value at the address in p |
nullptr | a pointer that points nowhere |
p->name | short for (*p).name |
References and nullptr
In the previous lessons you met references (int& ref = a;) — a reference is a second name for a variable. Unlike a pointer, a reference must be bound to a variable when it is declared and can never be re-bound to another one. A pointer, on the other hand, can be pointed at a different address at any time, or point nowhere at all.
#include <iostream>
int main() {
int a = 10;
int b = 20;
int& ref = a;
int* ptr = &a;
ref += 5;
std::cout << "a = " << a << '\n';
ptr = &b;
*ptr += 5;
std::cout << "b = " << b << '\n';
std::cout << "a = " << a << '\n';
return 0;
}a = 15 b = 25 a = 15
A pointer that points nowhere is set to nullptr. Dereferencing nullptr (*p) is undefined behaviour and usually crashes the program. So if a pointer may be empty, check it before use:
#include <iostream>
void printValue(const int* p) {
if (p == nullptr) {
std::cout << "No value\n";
return;
}
std::cout << "Value: " << *p << '\n';
}
int main() {
int level = 3;
printValue(&level);
printValue(nullptr);
return 0;
}Value: 3 No value
Dynamic memory: new and delete
Ordinary local variables are created on the stack and removed automatically when the function ends. Sometimes, though, you need to allocate memory while the program runs, in whatever size is needed. The new operator reserves space on the heap and returns its address. This memory is not freed by itself: you must release it by hand with delete (or delete[] for an array).
#include <iostream>
int main() {
int* p = new int(42);
std::cout << *p << '\n';
delete p;
p = nullptr;
int size = 4;
int* squares = new int[size];
for (int i = 0; i < size; i++) {
squares[i] = i * i;
}
std::cout << squares[3] << '\n';
delete[] squares;
return 0;
}42 9
RAII and smart pointers
Modern C++ solves these problems with the RAII principle (Resource Acquisition Is Initialization): an object takes ownership of a resource and gives it back automatically when the object is destroyed. std::vector and std::string work exactly like this. For a single object there is the smart pointer std::unique_ptr from <memory>: it is the sole owner of the memory and deletes it itself when it goes out of scope.
#include <iostream>
#include <memory>
#include <utility>
int main() {
std::unique_ptr<int> score = std::make_unique<int>(100);
*score += 50;
std::cout << "Score: " << *score << '\n';
std::unique_ptr<int> other = std::move(score);
std::cout << std::boolalpha << (score == nullptr) << '\n';
std::cout << "Other: " << *other << '\n';
return 0;
} // other deletes the memory automaticallyScore: 150 true Other: 150
unique_ptr cannot be copied; ownership can only be handed over with std::move, after which score is empty.Key points
- A pointer stores another variable's address:
int* p = &x;. &gives an address, and*reaches the value at an address.- A reference is always bound to one variable; a pointer can be re-pointed and can be
nullptr. - Memory from
newmust be freed withdelete(delete[]for arrays), or it leaks. - RAII:
std::vector,std::stringandstd::unique_ptrfree memory automatically — prefer them.
Check yourself
10 questions. Every correct answer earns XP.
&x give?