Week 4

What are we doing today?

  • How a program's memory is structured and maintained
  • References and pointers
  • Pass by reference/value
  • How to convert objects between different types

What is the output?

#include <iostream>
void increment(int a) {
  a = a + 5;
}
int main() {
  int var = 5;
  increment(var);
  std::cout << var << std::endl;
}

Memory structure

5dJ912z.png

  • All the memory in your computer is a huge, indexable space divided into bytes
  • Your program has three kinds of memory
    • Static memory: calculated at compile time
    • The stack: managed automatically by the language
    • The heap: your space to do whatever you want
      • You can ask for heap memory from the OS
      • You have to release/free what you've allocated to avoid memory leaks

What is a byte

  • 8 bits (0,1)
    • 00101010
    • 00010001
  • smallest indexable space on your computer

Object lifetimes stack

  • All variables have different lifetimes depending on where they are allocated in memory
  • Every method call has its own copy of the variables on the stack
#include <iostream>
void increment(int a) {
  int c = 5;
  a = a + 5;
}
int main() {
  int d = 6;
  int var = 5;
  increment(var);
  std::cout << var << std::endl;
}

Stack example

#include <iostream>
void increment(int a) {
  int c = 5;
  a = a + 5;
}
int main() {
  int d = 6;
  int var = 5;
  increment(var);
  std::cout << var << std::endl;
}

b6KnwB2.png

References

  • A reference is a variable that points to the same content as another variable
  • Why would you want that?
    • Share data across functions
    • Improve performance
  • You can't change where a reference points after initialization
  • You can mostly just treat references exactly like normal variables once they're initialized

Passing by reference

  • use the & next to a variable to make it pass by reference
#include <iostream>
void increment(int &a) {
  a = a + 5;
}
int main() {
  int var = 5;
  increment(var);
  std::cout << var << std::endl;
}

Pointers

  • A pointer is a special type of variable that "points to" another variable
    • the pointer variable stores the address of a variable
  • How are these different from references?
    • You have to explicitly get the value a pointer is pointing to
    • You can change where a pointer is pointing

How to get an address

  • The & operator is used to get an address of a variable
  • Use the * operator to denote a pointer variable
int main() {
  int var = 5;
  // TYPE* name;
  int* var_ptr = &var;
}

Example

int main() {
  int var = 5;
  // TYPE* name;
  int* var_ptr = &var;
}

HvxBD32.png

Dereferencing pointers

  • gets the value pointed to
    • here is an address, what is there?
  • use the * operator in front of a pointer to dereference it
  • use the -> operator behind a pointer to deference and do something else

Example

#include <iostream>
int main() {
  int var = 5;
  // TYPE* name;
  int* var_ptr = &var;
  std::cout << *var_ptr << std::endl;
}

7alG4QH.png

Another example

#include <iostream>
#include <string>

int main() {
  int var = 5;
  std::string s = "Pointers are fun";

  // TYPE* name;
  int* var_ptr = &var;

  std::string* str_ptr = &s;
  str_ptr->push_back('!');

  std::cout << "var_ptr is " << *var_ptr << std::endl;
  std::cout << "str_ptr has size " << str_ptr->size() << std::endl;
  std::cout << "Now s is: " << s << std::endl;
}

Pointer arithmatic

  • In c++ different datatypes have different sizes
  • A pointer will increment differently based on its datatype's size in bytes
type # bytes
char 1
int 4
double 8

Example int

#include <iostream>
int main() {
  int var = 6;
  int* var_ptr = &var;
  var_ptr += 1;
}

74Lb2KQ.png

Example char

#include <iostream>
int main() {
  char rj = 'c';
  char* char_ptr = &rj;
  char_ptr += 2;
}

vc7sR0R.png

New / Delete

  • To allocate memory on the heap you can use new
    • be careful how much you allocate
    • returns a pointer to the memory
  • To deallocate the memory you must use delete
    • anytime memory is allocated you must deallocate it
    • you pass the pointer to the memory you got from new
    • not deallocating is the cause of memory leaks
      • stay tuned for a demo

New

int main() {
  double *a = new double(10.0);
}

z3xGol7.png

Delete

int main() {
  double *a = new double(10.0);
  // ... legit code
  delete a;
}

A6a4bsz.png

Segfault

int main() {
  double *a = new double(10.0);
  // ... legit code
  delete a;
  // .. more code
  *a = 20.0
  // .. more code
}

Smart pointers

  • A smart pointer object manages a pointer
  • When the smart pointer is done (e.g. end of function), calls delete on its data
  • You should generally use one of these two types instead of a normal pointer:
    • shared_ptr: multiple shared_ptr objects can own a single pointer
    • unique_ptr: only one unique_ptr can own an pointer

unique_ptr

int main() {
  unique_ptr<object> u_ptr = make_unique<object>();
}

nK51rsP.png

shared_ptr

int main() {
  shared_ptr<object> s_ptr1 = make_shared<object>();
  // <--- HERE
  {
    shared_ptr<object> s_ptr2 = s_ptr1;
  }
  shared_ptr<object> s_ptr3 =  s_ptr1;
}

Sy6CY5q.png

shared_ptr

int main() {
  shared_ptr<object> s_ptr1 = make_shared<object>();
  {
    shared_ptr<object> s_ptr2 =  s_ptr1;
    // <--- HERE
  }
  shared_ptr<object> s_ptr3 =  s_ptr1;
}

y3ITWBn.png

shared_ptr

int main() {
  shared_ptr<object> s_ptr1 = make_shared<object>();
  {
    shared_ptr<object> s_ptr2 =  s_ptr1;
  }
  shared_ptr<object> s_ptr3 =  s_ptr1;
  // <--- HERE
}

LV3kigZ.png

Ownership

  • be careful about returning a shared pointer from a method
    • who owns what?

Casting, the wrong way

  • Sometimes you want to convert a variable to a different type: this is called casting
  • In C, you can simply change the type of a variable like so:
B* old_var = new B(); // some object
A* new_var = (A*)old_var;
  • The compiler will now treat the bytes of object old_var as though it was of type A
  • This is bad for type safety!

Type Safety

char c = 10;                        // this is one byte in memory

int *p = (int*) &c;                 // this is a 4-byte pointer pointing to one byte of
                                    // memory - it compiles but leads to corrupted memory
                                    // if you try to write to what p points to

int *q = static_cast<int*>(&c);     // throws an exception at compile time
  • Our second cast was type-safe, the first one was not
  • Type safety is a language feature that ensures that every variable you handle is actually the type you think it is
    • The compiler will check each time you assign a variable to ensure the types are compatible

Casting, the right way

  • C++ gives us functions that are type-safe, including compile-time checks
    • static_cast is mostly used for basic type conversions, e.g. between different types of numbers
    • dynamic_cast is mostly used for conversions between object types for polymorphism
    • reinterpret_cast works like a C-style cast and generally shouldn't be used unless you know exactly what you're doing

Exercise: line following with a twist

  • Modify line_follow.cpp
  • You should make 2 functions turn_right and turn_left
  • We can't use the function signature turn_left(RJRobot r)
    • This copies the RJRobot object, opening a second wifi link to the robot (which doesn't work)

Exercise cont'd

  • You can solve this problem several ways by changing the function signature. For practice, try these three:
    1. Pass a reference to the RJRobot
    2. Pass a pointer to the RJRobot (a non-smart pointer is fine)
    3. Pass no parameters
      • Declare a global (outside of any function) unique_ptr to an RJRobot
      • Don't forget to make_unique in main
      • You can use this unique_ptr in any function
  • Line follow algorithm:
    • repeat forever: if (sensor < threshold) then turn_left, else turn_right