Skip to content

Latest commit

 

History

History
546 lines (321 loc) · 10.4 KB

cpp-ref.org

File metadata and controls

546 lines (321 loc) · 10.4 KB

C++: Pointers, References & RAII

Quotes Makes You Sound Smurt

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

Pointers

The premium method for shooting yourself in the foot!

Pointers - What are they?

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);

Pointers - What are they for?

Passing around values that are too expensive to copy

struct bob_the_large_struct { /* ... */ } x;

do_a_thing(&x);

Pointers - What are they for?

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;

Pointers - Stack?

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;
 }

Pointers - Stack!

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;

Pointers - Heap?

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;

Pointers - Heap!

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;

Pointers - Stack + Heap = Stheap

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

Pointers - Bad Pointers! BAD!

Can be NULL

int *p = nullptr;
*p = 10; // Segfault

Pointers - Bad Pointers! BAD!

Can exceed the lifetime of the object it points to

int *p = nullptr;

{
    int x = 0;
    p = &x;
}

int y = 10;
*p = 20; // ???

References

(L-Value) References

Correct name but we’ll get back to that later.

References - What are they?

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);

References - What’s the Difference?

Is ALWAYS bound to a variable

int &r; // Error

References - What’s the Difference?

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 - So what?

References always point to valid memory*

References - Hey I saw that asterisk!

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

References - When to use them?

They’re strictly safer then pointers

Use them instead of pointers whenever possible

R-Value References

R-Value References - Heuh?

R-Values and L-Values references the side of the equal sign

Left = Right

R-Value References - L-Value

Left side of the equation

Is a definitive memory location

Has a name bound to it

R-Value References - R-Value

Right side of the equation

Is a temporary value that results of a computation

Does not have a name bound to it

R-Value References - What are they?

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

R-Value References - You lost me…

Used to indicate that an object should be MOVED

R-Value References - Copy vs Move

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

R-Value References - Baby steps…

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);

R-Value References - std::move?

Converts it’s argument into an R-Value reference

Indicates that we want the object to be moved and not copied.

R-Value References - Copy

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);

R-Value References - Move

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; }
};

R-Value References - Move II - Electric Boogaloo

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);

R-Value References - When to use them?

Whenever you’re handling data that should not be copied implicitly

Or when it’s more efficient to move the data then copy it

R-Value References - Usage Example

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)};

What about the other use case?

For data that should not be copied willy-nilly…

Ressource Allocation Is Initialization (RAII)

Ressource Allocation Is Initialization (RAII)

The C++ community is real good at naming things:

SFINAE: Substitution Failure Is Not An Error

The Most Vexing Parse

RAII - What is it?

When a resource is allocated…

… it should be captured by a constructor..

… and freed by the associated destructor.

Similar to using in python

RAII - Baby Steps

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);
}

RAII - std::unique_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

RAII - std::shared_ptr

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

RAII - Why use this?

Ties a resource’s lifetime to a given scope

Removes the burden of manually freeing resources

Works even when exceptions are thrown!

RAII - The Sky is the Limit!

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

RAII - This Is Real Life Folks!

{
    (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

RAII - Feel The Enlightment!

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++

Universal Reference

Universal Reference - Whaaaa?

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

Universal Reference - … sure bud… whatever you say…

template <typename Fn>
scope_exit<Fn> on_scope_exit(Fn &&fn)
{
    return scope_exit<Fn>(std::forward<Fn>(fn));
}

Universal Reference - Simplified

IF you’re writing a template function

THEN ask Bob

FIN