Pointers are real. They’re what the hardware understands. Somebody has to deal with them. You can’t just place a LISP book on top of an x86 chip and hope that the hardware learns about lambda calculus by osmosis. Denying the existence of pointers is like living in ancient Greece and denying the existence of Krackens and then being confused about why none of your ships ever make it to Morocco, or Ur-Morocco, or whatever Morocco was called back then. Pointers are like Krackens — real, living things that must be dealt with so that polite society can exist.
– James Mickens
The premium method for shooting yourself in the foot!
A variable that holds the address of another variable
int x = 0;
int *p = &x;
*p = 10;
assert(x == 10);
x = 20;
assert(*p == 20);
Passing around values that are too expensive to copy
struct bob_the_large_struct { /* ... */ } x;
do_a_thing(&x);
Referencing heap allocated values
struct bob_the_heap_struct { /* ... */ };
bob_the_heap_struct *p = new bob_the_heap_struct;
do_a_thing(p);
delete p;
Where all variables in a functions are placed
Very fast to allocate and is freed automatically on scope exit
Entirely managed by the compiler but limited to ~2Mb
void bob_the_function() {
int x = 0;
int *p = &x;
}
Returning a pointer to a stack allocated variable is bad joo-joo
int *bob_the_bad_function() {
int x = 0;
return &x;
}
int *p = bob_the_bad_function();
*p = 10;
Memory allocated manually outside of the stack
There’s a cost to allocating and must be freed manually
Allocation limit is whatever your computer can manage
int *p = new int;
*p = 10;
delete p;
No tied to a scope so can be passed around without issue
int *bob_the_better_function() {
int *p = new int;
return p;
}
int *p = bob_the_better_function();
*p = 10;
delete p;
For systems programming, knowing where things are stored is important
Stack vs Heap is a decision you have to make for every variable
Making the right decision will have a tremendous impact on performance
Can be NULL
int *p = nullptr;
*p = 10; // Segfault
Can exceed the lifetime of the object it points to
int *p = nullptr;
{
int x = 0;
p = &x;
}
int y = 10;
*p = 20; // ???
Correct name but we’ll get back to that later.
A variable that holds the address of another variable
int x = 0;
int &r = x;
r = 10;
assert(x == 10);
x = 20;
assert(r == 20);
Is ALWAYS bound to a variable
int &r; // Error
The binding is PERMANENT
int x = 0;
int &r = x;
int y = 10
r = y;
assert(x == y);
There’s no syntax to change what a reference points to.
References always point to valid memory*
Returning a reference to a temporary variable is possible
int &bob_the_mischievous_function() {
int x = 0;
return x;
}
int &r = bob_the_mischievous_function();
r = 10; // Nothing good
Compilers are pretty good at detecting this nowadays
They’re strictly safer then pointers
Use them instead of pointers whenever possible
R-Values and L-Values references the side of the equal sign
Left = Right
Left side of the equation
Is a definitive memory location
Has a name bound to it
Right side of the equation
Is a temporary value that results of a computation
Does not have a name bound to it
A reference to a temporary value
void bob_the_invalid_function(int &x) { /* ... */ }
bob_the_invalid_function(10); // Error
void bob_the_acceptable_function(const int &x) { /* ... */ }
bob_the_acceptable_function(10); // Ok but not mutable
void bob_the_confusing_function(int &&x) { /* ... */ }
bob_the_confusing_function(10); // Works!
Only ever used in function arguments
Used to indicate that an object should be MOVED
Copy: copy the content of an object so that both are EQUAL
Move: move the content of an object leaving the original EMPTY but VALID
std::vector<int> x = {1, 2, 3, 4, 5};
std::vector<int> a = x;
assert(x == a);
std::vector<int> b = std::move(x);
assert(x.size() == 0 && b.size() == 5);
Converts it’s argument into an R-Value reference
Indicates that we want the object to be moved and not copied.
struct bob_the_copy {
int x = 0;
bob_the_copy(const bob_the_copy &other) = default;
bob_the_copy& operator= (const bob_the_copy &other) = default;
};
bob_the_copy a{10};
bob_the_copy b{a};
assert(a.x == b.x);
a.x = 20;
b = a;
assert(a.x == b.x);
struct bob_the_moved {
HANDLE resource;
explicit bob_the_moved(HANDLE resourse) : resource(resource) {}
bob_the_moved(bob_the_moved &&other) : resource(other.resource) {
other.resource = INVALID_HANDLE;
}
bob_the_moved& operator= (bob_the_moved &&other) {
resource = other.resource;
other.resource = INVALID_HANDLE;
return *this;
}
operator bool() const { return resource != INVALID_HANDLE; }
};
bob_the_moved a{/* ... */};
assert(a);
bob_the_moved b{std::move(a)};
assert(!a && b);
bob_the_moved c{INVALID_HANDLE};
c = b;
assert(!a && !b && c);
Whenever you’re handling data that should not be copied implicitly
Or when it’s more efficient to move the data then copy it
struct bob_the_string {
std::string str;
explicit bob_the_string(std::string &&x) : str{x} {}
};
std::string a_long_long_long_long_string = /* ... */ ;
bob_the_string s{std::move(a_long_long_long_long_string)};
For data that should not be copied willy-nilly…
The C++ community is real good at naming things:
SFINAE: Substitution Failure Is Not An Error
The Most Vexing Parse
When a resource is allocated…
… it should be captured by a constructor..
… and freed by the associated destructor.
Similar to using in python
struct bob_the_raii
{
bob_the_raii(bob_the_struct *ptr) : ptr(ptr) {}
~bob_the_raii() { delete ptr; }
bob_the_raii(const bob_the_raii &other) = delete;
bob_the_raii& operator= (const bob_the_raii &other) = delete;
bob_the_struct *ptr;
};
{
bob_the_raii raii{ new bob_the_struct{10} };
do_a_thing(raii.ptr);
}
std::unique_ptr<bob_the_struct> ptr = std::make_unique<bob_the_struct>(10);
ptr->value = 20;
bob_the_struct x = *ptr;
auto copied = ptr; // ERROR
auto moved = std::move(ptr); // OK
Uses atomic reference counting to call delete
std::shared_ptr<bob_the_struct> ptr = std::make_shared<bob_the_struct>(10);
ptr->value = 20;
bob_the_struct x = *ptr;
auto copied = ptr; // reference count incremented
auto moved = std::move(ptr); // reference count not incremented
Ties a resource’s lifetime to a given scope
Removes the burden of manually freeing resources
Works even when exceptions are thrown!
std::mutex bob_the_mutex;
{
// will lock the mutex on construction
std::lock_guard guard{bob_the_mutex};
do_a_thing();
} // mutex is unlocked on destruction
{
(void) getaddrinfo(/* ... */, &list);
auto exit = on_scope_exit([=] { freeaddrinfo(list); });
for (struct addrinfo *it = list; it; it = it->ai_next)
do_a_thing(it);
} // the on_scope_exit lambda is called here which frees list
If used deligently, memory leaks are a non-issue
You should have no new, delete, malloc, free in your code ever
Corner Stone of Modern C++
IF you’re in a template function
AND one of your parameter is a reference parameterized by the template
THEN the reference should be a universal reference
template <typename Fn>
scope_exit<Fn> on_scope_exit(Fn &&fn)
{
return scope_exit<Fn>(std::forward<Fn>(fn));
}
IF you’re writing a template function
THEN ask Bob