Pointers
Pointers are just integers that represent memory addresses.
Raw Pointers
Defining Pointers
A pointer is just a memory address — an integer, so it doesn’t need a type. Giving a pointer a type just states that the data at that address is the type we specified.
Here we’ve defined a pointer with memory address 0
.
Note that 0
is not a valid memory address. It’s
the same as NULL
.
// These are all equivalent
void *ptr = 0;
void *ptr = NULL;
void *ptr = nullptr;
What if we want to create a useful pointer that points to a valid memory address, like a variable in our program?
int var = 16;
// &var is the memory address of var.
// int * is our way of telling what the data type is that
// ptr points to. Using void * would also be valid.
int *ptr = &var;
Using Pointers
We can use a pointer to manipulate the data stored at its
address, by dereferencing the pointer using a *
.
Using the snippet above as an example:
int var = 16;
int *ptr = &var;
// Writing from a pointer:
// Go to the address stored in ptr and set the value there to 13
*ptr = 13;
// Reading from a pointer:
// Retrieve the value at the address stored in ptr, save to myvar2
int myvar2 = *ptr;
We can allocate space on the heap, which returns a pointer to the beginning address, set all the values to 0, then clean up.
// Allocate 8 chars in heap, and return a pointer to first one
char *buffer = new char[8];
// Sweep 8 bytes starting at buffer and set each value to 0.
memset(buffer, 0, 8);
// Clean up allocated memory.
delete[] buffer;
Double, Triple Pointers
Pointers themselves are stored in memory, and have a memory address of their own. You can create a pointer whose memory address references that of another pointer. These are essentially just pointers to pointers.
An example of this:
int var = 16;
// Just like before, ptr1 is an int pointer
int *ptr1 = &var;
// Store address of ptr1 in ptr2
// ptr2 is a pointer to an int pointer, hence int **
int **ptr2 = &ptr1;
Smart Pointers
Smart pointers are a way to automate the process of allocating and freeing memory on the heap. They’re just a wrapper around a raw pointer.
With raw pointers, we would need to do:
// Allocate 8 chars in heap, and return a pointer to first one
char *buffer = new char[8];
// ... do something with buffer ...
// Clean up allocated memory.
delete[] buffer;
Different types of smart pointers:
-
std::unique_ptr
: When this pointer goes out of scope, it will be destroyed (delete
will be called on it). These are scoped, so you can’t copy aunique_ptr
, because when one copy of it dies, that memory is freed so all other copies just reference freed memory. *
Using Smart Pointers
The first thing we’ll have to do to get access to these smart pointers is to #include <memory>
.
We’ll be using the following class to demonstrate the functionality of smart pointers:
#include <iostream>
#include <string>
#include <memory>
// Class to show creation/destruction of smart pointer
class Entity {
public:
// Constructor
Entity() {
std::cout << "Created Entity!\n";
}
// Destructor
~Entity() {
std::cout << "Destroyed Entity!\n";
}
void entityPrint() {
std::cout << "print() invoked\n";
}
};
std::unique_ptr
If we want to create a pointer that lives in a certain scope, we can do that like so:
int main() {
{
std::unique_ptr<Entity> entity = std::make_unique<Entity>();
entity->entityPrint();
}
}
You can only have 1 copy in existence for the life of a std::unique_ptr . Its life and death is within the scope that it was defined in. For example, you cannot do something like
std::unique_ptr<Entity> e2 = entity , this would not even compile.
|
std::shared_ptr
By using shared_ptr
, more than one pointer can point
to this one object at a time and it’ll maintain a
Reference Counter using the use_count()
method
(GeeksForGeeks).
Each time a shared pointer falls out of scope,
the allocated object’s reference counter is decremented.
Once the reference count reaches zero, the object is
deallocated and freed.
Example:
int main() {
std::shared_ptr<Entity> e2;
{
std::shared_ptr<Entity> e1 = std::make_shared<Entity>();
std::cout << e1.use_count() << '\n';
e2 = e1;
e2->entityPrint();
std::cout << e2.use_count() << '\n';
}
std::cout << e2.use_count() << '\n';
}
Example output:
Entity constructed()
1
print() invoked
2
1
Entity destructed()