1 of 123

Introduction to Smart Pointers and Why

1

12:10-13:10, Tue, 6th September 2022

60 minutes | Introductory Audience

2 of 123

Introduction to Smart Pointers and Why

2

12:10-13:10, Tue, 6th September 2022

60 minutes | Introductory Audience

Some emphasis on this being an introduction today

3 of 123

Introduction to Smart Pointers and Why

3

12:10-13:10, Tue, 6th September 2022

60 minutes | Introductory Audience

The ‘why’ turned this into a ‘systems look’ regarding memory that should be useful for your foundational knowledge

4 of 123

Please do not redistribute slides without prior permission.

4

5 of 123

Goal(s) for today

5

6 of 123

What you’re going to learn

  • For Students and C++ Learners
    • Learn a bit about memory -- that’s the foundation
    • Learn a bit about pointers -- And think about why they are necessary and some of their powers
    • Learn about smart pointers -- how they improve on raw pointers
      • And why we might want to use smart pointers
  • This is a ‘back to basics’ style talk

6

7 of 123

Your Tour Guide for Today�by Mike Shah (he/him)

  • Associate Teaching Professor at Northeastern University in Boston, Massachusetts.
    • I teach courses in computer systems, computer graphics, and game engine development.
    • My research in program analysis is related to performance building static/dynamic analysis and software visualization tools.
  • I do consulting and technical training on modern C++, Concurrency, OpenGL, and Vulkan projects
    • (Usually graphics or games related)
  • I like teaching, guitar, running, weight training, and anything in computer science under the domain of computer graphics, visualization, concurrency, and parallelism.
  • Contact information and more on: www.mshah.io
  • More online training coming at courses.mshah.io

7

8 of 123

Code for the talk

8

9 of 123

Abstract

Pointers are one of the most powerful tools in the C++ language available to programmers. Pointers allow sharing of resources, and for programmers to take control of the lifetime of their objects. However-- 'with great power comes great responsibility'. In this talk I am going to begin with how programmers can 'wrap' a raw pointer in a class to create a smart pointer that takes advantage of RAII to reclaim resources. I will then introduce in the standard library each of unique_ptr, shared_ptr, and weak_ptr. Beginners who have never seen these before will leave with concrete advice on where to use each of the following and for what scenarios where they previously had used raw pointers. Finally, at the end of the talk, I will give a brief insight into Smart pointer adaptors in C++23 (out_ptr_t, out_ptr, inout_ptr_t, and inout_ptr).

9

The abstract that you read and enticed you to join me is here!

10 of 123

Prerequisite Knowledge for this Presentation

10

11 of 123

What is a raw pointer?

  • If this picture makes sense to you, and you can explain in your own words what a pointer is -- let’s proceed!

11

42

0xf8888888

0xf8888888

0xf8888884

int x = 42;�

(int)

int* px = &x;

�(pointer to integer)

12 of 123

Prerequisite Refresher

12

13 of 123

Let’s start with memory

(And there are many kinds of memory)

13

14 of 123

We have many types of physical memory

  • The goal of memory is to store ‘data’
    • The duration of that storage could vary depending on the storage medium
      • (e.g. a hard drive or cloud storage should store information indefinitely)
  • (Aside: As an expert, you’re probably thinking more about the mediums, allocators, allocation size, where the memory lives, data access patterns, data lifetime and various ‘misses’ that can occur in different caches.)

14

15 of 123

Memory Hierarchy (1/2)

  • Understanding the different purposes of each physical medium is actually very important for performance!
  • However -- for this talk we can just think about having access to a bunch of ‘working memory’

15

16 of 123

Memory Hierarchy (2/2)

  • Understanding the different purposes of each physical medium is actually very important for performance!
  • However -- for this talk we can just think about having access to a bunch of ‘working memory’
    • This is thanks to a very important mechanism available in most popular desktop operating systems known as virtual memory [wiki]
    • virtual memory allows programmers to ‘think’ different segments of memory (i.e. stack and heap, which we’ll get to) are each given to us in simple contiguous segments.

16

17 of 123

Programmers View of working (or ‘main’) memory (1/3)

  • So roughly speaking we have a contiguous block of memory that looks something like this.

17

Address (in Hex)

Value

0x1000000B

0x1000000A

0x10000009

0x10000008

0x10000007

0x10000006

0x10000005

0x10000004

0x10000003

0x10000002

0x10000001

0x10000000

18 of 123

Programmers View of working (or ‘main’) memory (2/3)

  • So roughly speaking we have a contiguous block of memory that looks something like this.
  • When we create a variable, a certain amount of that storage is allocated for that variable.

18

Address (in Hex)

Value

0x1000000B

0x1000000A

0x10000009

0x10000008

0x10000007

0x10000006

0x10000005

0x10000004

0x10000003

0x10000002

0x10000001

0x10000000

19 of 123

Programmers View of working (or ‘main’) memory (3/3)

  • So roughly speaking we have a contiguous block of memory that looks something like this.
  • When we create a variable, a certain amount of that storage is allocated for that variable.
    • for example
      • int x = 42;
      • (int is usually 4 bytes, thus 4 bytes taken in the illustration where 1 box = 1 byte of memory)

19

Address (in Hex)

Value

0x1000000B

0x1000000A

0x10000009

0x10000008

0x10000007

0x10000006

0x10000005

0x10000004

0x10000003

0x10000002

0x10000001

0x10000000

42

20 of 123

Operating System View of Processes (1/3)

  • Now keep in mind we have many programs running at once
    • So per process our operating system has given some memory allocated to each process.

20

20

Address (in Hex)

Value

0x10000000 �+ N bytes

...

...

...

...

...

...

...

...

...

...

0x10000000

21 of 123

Operating System View of Processes (2/3)

  • Now keep in mind we have many programs running at once
    • So per process our operating system has given some memory allocated to each process.
      • Here are two processes for example

21

Address (in Hex)

Value

0x20000000 + M

...

...

0x20000000

...

...

...

...

0x10000000 + N

...

...

0x10000000

Process B

Process A

22 of 123

Operating System View of Processes (3/3)

  • Now keep in mind we have many programs running at once
    • So per process our operating system has given some memory allocated to each process.
      • Here are two processes for example
    • Let’s focus on just one process and zoom in a little bit on the memory in that single process

22

Address (in Hex)

Value

0x20000000 + M

...

...

0x20000000

...

...

...

...

0x10000000 + N

...

...

0x10000000

Process B

Process A

23 of 123

A Single Processes Memory Segments (1/3)

  • A process (i.e. a program running on your program) is organized into a few segments of memory

23

Address (in Hex)

Value

0x10000000 + N

...

...

...

...

...

...

...

...

...

0x10000000

Process A

24 of 123

A Single Processes Memory Segments (2/3)

  • A process (i.e. a program running on your program) is organized into a few segments of memory
    • (Read from bottom to top)
      • code (or .text) is where our code is
      • data for initialized data
      • heap for dynamically allocated memory
      • stack for our ‘temporary’ memory
      • (Aside: There may be many other segments as well -- use otool or objdump to see other sections like debug, static, bss, exception, etc.)

24

Address (in Hex)

Value

0x10000000 + N

...

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

25 of 123

A Single Processes Memory Segments (3/3)

  • A process (i.e. a program running on your program) is organized into a few segments of memory
    • (Read from bottom to top)
      • code (or .text) is where our code is
      • data for initialized data
      • heap for dynamically allocated memory
      • stack for our ‘temporary’ memory
      • (Aside: There may be many other segments as well -- use otool or objdump to see other sections like debug, static, bss, exception, etc.)

25

Address (in Hex)

Value

0x10000000 + N

...

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Let’s start with the stack

26 of 123

A Process and its stack memory (1/5)

26

Address (in Hex)

Value

0x16d5ff988

...

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

27 of 123

A Process and its stack memory (2/5)

  • Executing this line, we’ll allocate on the ‘stack’ space for x.
    • x stores the value ‘42’

27

Address (in Hex)

Value

0x16d5ff988

...

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

28 of 123

A Process and its stack memory (3/5)

  • Executing this line, we’ll allocate on the ‘stack’ space for x.
    • x stores the value ‘42’

28

Address (in Hex)

Value

0x16d5ff988

...

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

42

29 of 123

A Process and its stack memory (4/5)

  • And we can use ‘&’ operator to retrieve that actual stack address!

29

Address (in Hex)

Value

0x16d5ff988

...

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

42

30 of 123

A Process and its stack memory (5/5)

  • And we can use ‘&’ operator to retrieve that actual stack address!

30

Address (in Hex)

Value

0x16d5ff988

...

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

42

31 of 123

Stack Allocation With Multiple Variables (1/4)

  • Here’s another example showing what happens when you allocate multiple variables.
    • The stack grows downward in the order of the allocations.

31

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

32 of 123

Stack Allocation With Multiple Variables (2/4)

  • Here’s another example showing what happens when you allocate multiple variables.
    • The stack grows downward in the order of the allocations.

32

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

(x) 42

33 of 123

Stack Allocation With Multiple Variables (3/4)

  • Here’s another example showing what happens when you allocate multiple variables.
    • The stack grows downward in the order of the allocations.

33

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

(x) 42

(y) 42

34 of 123

Stack Allocation With Multiple Variables (4/4)

  • Observe the memory addresses growing downward

34

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

(x) 42

(y) 42

35 of 123

Automatic Memory Management -- Local Variables Popped off stack (1/5)

  • Now when we reach the end of our current scope
    • Anything allocated on the stack within the ‘block scope’ (i.e. current stack frame) will be ‘popped’ off the stack
    • This is effectively done by moving a ‘stack pointer’ to the next available location to overwrite.

35

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

(x) 42

(y) 42

36 of 123

Automatic Memory Management -- Local Variables Popped off stack (2/5)

  • Now when we reach the end of our current scope
    • Anything allocated on the stack within the ‘block scope’ (i.e. current stack frame) will be ‘popped’ off the stack
    • This is effectively done by moving a ‘stack pointer’ to the next available location to overwrite.

36

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

(x) 42

(y) 42

Stack Pointer

37 of 123

Automatic Memory Management -- Local Variables Popped off stack (3/5)

  • Now when we reach the end of our current scope
    • Anything allocated on the stack within the ‘block scope’ (i.e. current stack frame) will be ‘popped’ off the stack
    • This is effectively done by moving a ‘stack pointer’ to the next available location to overwrite.

37

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

(x) 42

???

Stack Pointer

38 of 123

Automatic Memory Management -- Local Variables Popped off stack (4/5)

  • Now when we reach the end of our current scope
    • Anything allocated on the stack within the ‘block scope’ (i.e. current stack frame) will be ‘popped’ off the stack
    • This is effectively done by moving a ‘stack pointer’ to the next available location to overwrite.

38

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

???

???

Stack Pointer

39 of 123

Automatic Memory Management -- Local Variables Popped off stack (5/5)

  • Now when we reach the end of our current scope
    • Anything allocated on the stack within the ‘block scope’ (i.e. current stack frame) will be ‘popped’ off the stack
    • This is effectively done by moving a ‘stack pointer’ to the next available location to overwrite.

39

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

???

???

Stack Pointer

So at this point:

  • We understand that stack memory is automatically managed for us
    • Memory is placed on the stack
    • Stack allocated memory will be reclaimed at the end of its block scope
  • Pretty simple model!
    • (And for performance working with memory on the stack is fast!)

40 of 123

Audience Poll: How much data do you see here?

a.) A lot

b.) Very little

40

Nanite | Inside Unreal�https://youtu.be/TMorJX3Nj6U?t=5255

41 of 123

Audience Poll: How much data do you see here?

a.) A lot

b.) Very little

41

Nanite | Inside Unreal�https://youtu.be/TMorJX3Nj6U?t=5255

Humor me here -- there’s ‘a lot’ of data (and that is probably an understatement!)

42 of 123

Can we store all this data on the stack? (1/2)

  • So my question is, can we store all of this data on our ‘stack’?
    • Let’s assume this is several gigabytes of data for arguments sake, and that a good chunk of that is on the CPU
    • (Dear experts :) -- just assume there is a lot of data, even if this is a more CPU/GPU memory bound example)

42

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Stack Pointer

43 of 123

Can we store all this data on the stack? (2/2)

  • So my question is, can we store all of this data on our ‘stack’?
    • Let’s assume this is several gigabytes of data for arguments sake, and that a good chunk of that is on the CPU
    • (Dear experts :) -- just assume there is a lot of data, even if this is a more CPU/GPU memory bound example)

43

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Stack Pointer

  • The answer is no.
  • Our stack grows downward, and would overflow and overwrite other sensitive segments of our running processes memory
    • So we have another mechanism for ‘large allocations’ or otherwise allocations we cannot figure out at compile-time

44 of 123

Heap Memory

For Dynamic Memory Allocations

44

44

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

45 of 123

Heap Memory

45

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

  • Heap memory is memory that we allocate at ‘run-time’
  • ‘The heap’ is some sort of data structure that stores our successful requests for memory.
    • In order to use that memory, we need a ‘pointer’ which stores that address of memory
    • more on pointers in a second, but let’s look at the ‘heap’ in our visualization.

46 of 123

Heap Visualization (1/8)

46

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

  • The heap data structure might look something like this
  • A ‘large collection of bytes

0x70000000

0x70000000 + N

47 of 123

Heap Visualization (2/8)

47

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

  • We ‘manually’ allocate memory using new
    • e.g. int* data = new int;

4

0x70000000

0x70000000 + N

48 of 123

Heap Visualization (3/8)

48

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

  • We ‘manually’ allocate memory using new
    • e.g. int* data = new int;
    • And assign with: *data = 77;

77

4

0x70000000

0x70000000 + N

49 of 123

Heap Visualization (4/8)

49

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

  • We ‘manually’ allocate memory using new
    • e.g. int* data = new int;
    • And assign with: *data = 77;

77

4

0x70000000

0x70000000 + N

(Aside)

  • The ‘4’ at the start does some bookkeeping in our heap structure to tell us how big the allocation was.
  • The actual data we write an integer to (again we need 4 bytes) could come after the little ‘header’ labeled 4 that does bookkeeping for us.

50 of 123

Heap Visualization (5/8)

50

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

  • We ‘manually’ allocate memory using new
    • e.g. int* data = new int;
    • And assign with: *data = 77;

77

4

0x70000000

0x70000000 + N

  • So ‘new int’ returned an address to some new memory.
  • In order to store an address, we need a special data type, known as a ‘pointer’ (i.e. int*)

51 of 123

Heap Visualization (6/8)

51

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

  • We ‘manually’ allocate memory using new
    • e.g. int* data = new int;
    • And assign with: *data = 77;

77

4

0x70000000

0x70000000 + N

  • Now before our program terminates, we’re going to need to delete the memory ‘manually’ that we have allocated.

52 of 123

Heap Visualization (7/8)

52

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

  • Heap memory is usually meant to be long lived
    • (It gets its own section for that reason)

77

4

0x70000000

0x70000000 + N

  • So we use delete data; to reclaim the memory in our process.

53 of 123

Heap Visualization (8/8)

53

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

  • Heap memory is usually meant to be long lived
    • (It gets its own section for that reason)

????????

?

0x70000000

0x70000000 + N

  • So we’ll use delete data; to reclaim the memory in our process.
    • Now our previous block of memory in the heap can be repurposed.

54 of 123

Let’s take a quick breather and recap

54

55 of 123

Recap of Stack and Heap (1/2)

  • A running process is broken into several segments of memory
    • The stack is automatically managed memory
      • Big or too many allocations are a problem and and cause a stack to ‘overflow’ into other segments.
    • The heap is memory that we manage larger and usually longer lived objects manually using ‘new’ and ‘delete’
      • A pointer is the data type that can hold an address to this allocated memory
      • One purpose of pointers is for us to acquire dynamically allocated memory on the heap.

55

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

56 of 123

Recap of Stack and Heap (2/2)

  • A running process is broken into several segments of memory
    • The stack is automatically managed memory
      • Big or too many allocations are a problem and and cause a stack to ‘overflow’ into other segments.
    • The heap is memory that we manage larger and usually longer lived objects manually using ‘new’ and ‘delete’
      • A pointer is the data type that can hold an address to this allocated memory
      • One purpose of pointers is for us to acquire dynamically allocated memory on the heap.

56

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Let’s take a closer look at heap allocations.

Heap allocated memory means we have the added responsibility of manually managing these objects lifetime.

57 of 123

Heap Allocations

57

58 of 123

Heap Allocation Responsibility (1/5)

  • Let’s take a look at what happens when we allocate memory on the heap.
  • In this program, we allocate a new int in every iteration of the loop

58

Heap Memory

59 of 123

Heap Allocation Responsibility (2/5)

  • Let’s take a look at what happens when we allocate memory on the heap.
  • In this program, we allocate a new int in every iteration of the loop

59

First integer allocation in our loop

4

Heap Memory

60 of 123

Heap Allocation Responsibility (3/5)

  • Let’s take a look at what happens when we allocate memory on the heap.
  • In this program, we allocate a new int in every iteration of the loop

60

First integer allocation in our loop

4

Heap Memory

Second allocation in our loop

4

61 of 123

Heap Allocation Responsibility (4/5)

  • Let’s take a look at what happens when we allocate memory on the heap.
  • In this program, we allocate a new int in every iteration of the loop

61

First integer allocation in our loop

4

Heap Memory

....

4

Nth integer allocation in our loop

4

62 of 123

Heap Allocation Responsibility (5/5)

  • There is one major problem I want to focus on here
    • We are leaking memory!
    • (The other problem is that we probably just want to stack allocate this int, but this is just an example!)

62

First integer allocation in our loop

4

Heap Memory

....

4

Nth integer allocation in our loop

4

63 of 123

Leak Visual (1/9)

63

Heap Memory

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Every time, I run this code, I allocate on the stack ‘allocateResource’.

The memory ‘allocateResource’ points to is on the heap.

But when I exit scope (in this case restart the loop), I lose that pointer on the stack!

64 of 123

Leak Visual (2/9)

64

Heap Memory

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Every time, I run this code, I allocate on the stack ‘allocateResource’.

The memory ‘allocateResource’ points to is on the heap.

But when I exit scope (in this case restart the loop), I lose that pointer on the stack!

65 of 123

Leak Visual (3/9)

65

Heap Memory

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Every time, I run this code, I allocate on the stack ‘allocateResource’.

The memory ‘allocateResource’ points to is on the heap.

But when I exit scope (in this case restart the loop), I lose that pointer on the stack!

allocateResource = 0;

66 of 123

Leak Visual (4/9)

66

Heap Memory

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Every time, I run this code, I allocate on the stack ‘allocateResource’.

The memory ‘allocateResource’ points to is on the heap.

But when I exit scope (in this case restart the loop), I lose that pointer on the stack!

allocateResource = 0;

67 of 123

Leak Visual (5/9)

67

Heap Memory

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Every time, I run this code, I allocate on the stack ‘allocateResource’.

The memory ‘allocateResource’ points to is on the heap.

But when I exit scope (in this case restart the loop), I lose that pointer on the stack!

allocateResource = 0xff123456;

First integer allocation in our loop

4

68 of 123

Leak Visual (6/9)

68

Heap Memory

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Every time, I run this code, I allocate on the stack ‘allocateResource’.

The memory ‘allocateResource’ points to is on the heap.

But when I exit scope (in this case restart the loop), I lose that pointer on the stack!

?????

First integer allocation in our loop

4

69 of 123

Leak Visual (7/9)

69

Heap Memory

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Every time, I run this code, I allocate on the stack ‘allocateResource’.

The memory ‘allocateResource’ points to is on the heap.

But when I exit scope (in this case restart the loop), I lose that pointer on the stack!

?????

First integer allocation in our loop

4

70 of 123

Leak Visual (8/9)

70

Heap Memory

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

Every time, I run this code, I allocate on the stack ‘allocateResource’.

The memory ‘allocateResource’ points to is on the heap.

But when I exit scope (in this case restart the loop), I lose that pointer on the stack!

First integer allocation in our loop

4

allocateResource = 0xFF122432;

Second allocation in our loop

4

71 of 123

Leak Visual (9/9)

71

Heap Memory

Address (in Hex)

Value

0x16d7f38d8

0x16d7f38d4

...

...

...

...

...

...

...

...

0x10000000

Stack

Code

Data

Heap

And we have no pointer now to this first allocation.

This is the ‘leak’ as this resource will remain for our programs duration

First integer allocation in our loop

4

allocateResource = 0xFF122432;

Second allocation in our loop

4

72 of 123

Memory Leak Fix

72

One fix is to ‘delete allocateResource’ within the scope.

73 of 123

Memory Leak Fix - Slight code update (1/2)

  • We do have to be careful though!
  • ‘Some’ programmer (blame me) came in at line 5 and decided to allocate 500 int’s.
    • delete and delete[] do not do the same thing!
      • delete will only free the first element of this array, so we are still leaking 499 integers!

73

74 of 123

Memory Leak Fix - Slight code update (2/2)

74

This time we use ‘delete[] allocateResource

(The left side has a memory leak, the right side is correct now)

75 of 123

Not always so obvious though on where to delete

  • I’m being a little difficult, but realistic-- it’s not always obvious where to delete something
    • What if we are allocating in other functions?
    • What if the object gets stored in a longer lived data structure?
  • We *really* have to think about how to reclaim our resources
    • Let’s remind ourselves when to things get cleaned up in C++ on the next slide!

75

76 of 123

Reminder on Object Destruction RAII

  • Heap
    • In heap memory (using new), when we explicitly called delete or delete[]
  • Stack
    • In stack memory, objects are freed within the scope they were declared�
  • When objects are destroyed, the ‘destructor’ is called!
    • Hmm, maybe we can use this to our advantage!

76

77 of 123

Building a Smarter Pointer

(By wrapping a raw pointer)

77

78 of 123

Wrap Raw Pointer allocating on the heap and utilize RAII

  • We can start to ‘wrap’ a raw pointer.
  • Here’s an example where we at the very least will have ‘delete’ called when we exit the scope of ‘s’

78

79 of 123

Safety with RAII -- called destructor

  • This more ‘contrived’ example shows that even if we throw an exception, the stack will eventually unwind and call our destructor on ‘s’.
    • If this was a call to ‘new’ at line 21, and delete was after line 22, we may not free the memory ever!
      • (Note: In this slideware since we ‘exit(1) so it really does not matter, but you will see the the destructor called)

79

80 of 123

Constructing a Smarter Pointer - template

  • So we can start to see some benefits of a ‘Smart Pointer’ class to help prevent some types of resource leaks
    • Let’s make it a template so we can store any type of data.

80

81 of 123

Constructing a Smarter Pointer - delete/delete[]

  • Let’s also handle delete versus delete[] in our constructor.
    • Many ways we can handle this (either separate implementations, template argument with a policy, overloads)
    • By keeping track of size of allocation, we can use the correct delete and have one less detail to accidentally make a mistake!!

81

82 of 123

Constructing a Smarter Pointer - operator=

  • Now, eventually we’ll want to do something useful with our SmartPtr class, like assign, dereference, maybe copy, etc.
  • Let’s look at assignment for now.
    • (Note: The other update I made was a ‘destroy member function to help clean things up)

82

83 of 123

A Smart Pointer that shares data (1/11)

  • So one of the powers of pointers is that we can ‘share’ data.
    • i.e. updating one piece of data ‘s2’ in this case will update the rest of the pointers that point to that data source.
      • They all point to the same ‘chunk’ of memory that is heap allocated.

83

84 of 123

A Smart Pointer that shares data (2/11)

  • So one of the powers of pointers is that we can ‘share’ data.
    • i.e. updating one piece of data ‘s2’ in this case will update the rest of the pointers that point to that data source.
      • They all point to the same ‘chunk’ of memory that is heap allocated.

84

84

Heap Memory

s data

s

85 of 123

A Smart Pointer that shares data (3/11)

  • So one of the powers of pointers is that we can ‘share’ data.
    • i.e. updating one piece of data ‘s2’ in this case will update the rest of the pointers that point to that data source.
      • They all point to the same ‘chunk’ of memory that is heap allocated.

85

85

Heap Memory

s data

s

s1 data

s1

86 of 123

A Smart Pointer that shares data (4/11)

  • So one of the powers of pointers is that we can ‘share’ data.
    • i.e. updating one piece of data ‘s2’ in this case will update the rest of the pointers that point to that data source.
      • They all point to the same ‘chunk’ of memory that is heap allocated.

86

86

Heap Memory

s data

s

s1 data

s1

s2 data

s2

87 of 123

A Smart Pointer that shares data (5/11)

  • So one of the powers of pointers is that we can ‘share’ data.
    • i.e. updating one piece of data ‘s2’ in this case will update the rest of the pointers that point to that data source.
      • They all point to the same ‘chunk’ of memory that is heap allocated.

87

Heap Memory

s data

s

s1 data

s1

s2 data

s2

88 of 123

A Smart Pointer that shares data (6/11)

  • So one of the powers of pointers is that we can ‘share’ data.
    • i.e. updating one piece of data ‘s2’ in this case will update the rest of the pointers that point to that data source.
      • They all point to the same ‘chunk’ of memory that is heap allocated.

88

Heap Memory

s2 data (s data deleted)

s

s1 data

s1

s2 data

s2

89 of 123

A Smart Pointer that shares data (7/11)

  • So one of the powers of pointers is that we can ‘share’ data.
    • i.e. updating one piece of data ‘s2’ in this case will update the rest of the pointers that point to that data source.
      • They all point to the same ‘chunk’ of memory that is heap allocated.

89

Heap Memory

s2 data (s data deleted)

s

s1 data

s1

s2 data

s2

90 of 123

A Smart Pointer that shares data (8/11)

  • So one of the powers of pointers is that we can ‘share’ data.
    • i.e. updating one piece of data ‘s2’ in this case will update the rest of the pointers that point to that data source.
      • They all point to the same ‘chunk’ of memory that is heap allocated.

90

Heap Memory

s2 data (s data deleted)

s

s2 data (s1 data deleted)

s1

s2 data

s2

91 of 123

A Smart Pointer that shares data (9/11)

  • So here’s our picture of the heap memory
    • One problem though (next slide)

91

Heap Memory

s2 data (s data deleted)

s

s2 data (s1 data deleted)

s1

s2 data

s2

92 of 123

A Smart Pointer that shares data (10/11)

  • So here’s our picture of the heap memory
    • One problem though (next slide)

92

Heap Memory

s2 data (s data deleted)

s

s2 data (s1 data deleted)

s1

s2 data

s2

93 of 123

A Smart Pointer that shares data (11/11)

  • So here’s our picture of the heap memory
    • One problem though (next slide)

93

Heap Memory

s2 data (s data deleted)

s

s2 data (s1 data deleted)

s1

s2 data

s2

As soon as one of the destructors is called, recall each SmartPtr points to the same piece of now deleted memory. This gives us a double free error.

94 of 123

Recap

  • We’ve taken some time to understand heap allocations
    • We need to be careful to avoid a memory leak by forgetting to delete (and call the right delete operator delete or delete[]).
  • We can try to avoid memory leak by ‘wrapping’ our pointer and utilizing RAII
  • However, when sharing pointers we have to be careful, with double free error -- and we have to think about ownership.

94

95 of 123

Smart Pointer Design Decisions on ownership

95

96 of 123

Understanding Ownership

  • So, the culprit for the ‘double free’ was really about ownership
    • We have to ask the question of which ‘SmartPtr’ is responsible for cleanup of one single resource.

96

97 of 123

Understanding Ownership -- Unique Owner (1/2)

  • One strategy could be to disallow sharing
    • We can delete the operator assignment and copy constructor
      • (or pre C++11 make private the member functions).
  • This is a valid strategy

97

98 of 123

Understanding Ownership -- Unique Owner (2/2)

  • BUT
    • We do want to be able use ‘move’ semantics still.
  • std::move is legal, and preserves the property that only one owner of the final resource
    • (Thus preventing a double-free)

98

99 of 123

Understanding Ownership -- More than one owner? (1/2)

  • BUT, what if we really do want multiple pointers to point to the same block of memory?
  • We can do some bookkeeping!
    • Using a reference counting strategy
    • The implementation on the right is not correct though! (even for slideware...it’s going to cause lots of problems, and not even compile!)

99

100 of 123

Understanding Ownership -- More than one owner? (2/2)

  • Let’s capture these two ideas though -- this is what is valuable
    • 1.) Increase reference count anytime we assign to the same piece of memory
    • 2.) Only delete/delete[] if our reference count is 0.
      • (Note: If we can’t delete the memory, we should still in our destructor decrement the ‘global’ reference count)

100

101 of 123

Design of Smart Pointers that can share

  1. In order to increase reference count, we need another structure that can atomically update
    1. (note: the atomicity of the reference counting is something I’d like to measure for performance to compare to raw pointers. Current/future Audience insights welcome! [One ref Josuttis 19])
  2. This control block for our shared pointer will also have to be allocated, and eventually freed when the reference count goes to zero.

101

SmartPtr<int> s

SmartPtr<int> s1

SmartPtr<int> s2

Control Block “The s’s”

atomic<int> references = 3

102 of 123

Core cpp 2022

  • A Note on my note below about performance, maybe check out the session after mine!
  • (note: the atomicity of the reference counting is something I’d like to measure for performance to compare to raw pointers. Current/future Audience insights welcome! [One ref Josuttis 19])

102

103 of 123

Using our Smart Pointer (1/2)

  • Simple enough to implement some operators now (e.g. dereference non-const version, ->, []’s, etc.)
    • But (next slide)

103

104 of 123

Using our Smart Pointer (2/2)

  • Turns out we could do some more nifty things like handle errors and check our reference count
    • This is for our ‘Shared_SmartPointer’
  • We may want to handle this condition differently yet again though if our reference count is 0
    • We’ll find out about weak_ptr in C++
    • The idea is we can also have a separate reference count that is less strict.
      • i.e. if at least one object exists that’s nice, and we’ll use the result, but if not, being null is fine and we’ll handle that case.
      • i.e. is use-after-free (another memory problem!) tolerable

104

105 of 123

C++ 11 Smart Pointers in STL

#include <memory>

105

106 of 123

#include <memory> for Three STL Smart Pointers

  • STL Smart pointers are a ‘proxy’ in the sense that we can use them in place of raw pointers.
  • We construct the pointer using one of the following types
    • std::unique_ptr
    • std::shared_ptr
    • std::weak_ptr

106

107 of 123

What problem does a smart pointer solve?

  • Several of these issues we have discussed, but in summary
    • We don’t have to call ‘delete/delete[]’ explicitly anymore!
    • We can (and should) avoid calling ‘new’
      • (e.g. use make_shared or make_unique)
    • We can handle exceptions during the pointer creation(e.g. make_shared or make_unique)
  • By choosing each type of smart pointer, we ultimately are enforcing constraints on sharing and ownership, showing our design intent
  • STL Smart Pointers are a drop-in replacement for raw pointers to give us the advantages described above.

107

108 of 123

std::unique_ptr

  • Scoped pointer
    • When it goes out of scope, it will automatically be deleted.
  • We cannot copy them
    • This avoids the ‘double free’ issue
    • Can be your ‘default’ if you want to be very careful with your pointers, and do not intend on sharing data.
  • We also cannot assign unique_ptr to something else, it has to be unique.
  • We prefer the std::make_unique call generally (see comments)

108

109 of 123

std::shared_ptr

  • Allows a pointer to have multiple things pointing to it.
    • As long as other pointers are pointing to that memory, the memory will not be deleted.
  • Internally ‘reference counting’ or otherwise keeping track of how many things point to it is taking place
    • If nothing is pointing to it, then the pointer can safely be deleted
      • (This is a similar idea to how garbage collection works in Java)
      • (Now you know why there is no ‘delete’ in Java :))

109

110 of 123

std::weak_ptr

  • Similar to a shared pointer, but it does not increase the ‘reference count’
    • (Updates a separate weak_count)
  • In this way, you can have ‘invalid’ pointers
    • Sometimes you do not care however, and maybe you just want a lightweight way to point to some references.
    • e.g. You have a GameObject that was blown up mid-way in the game while other objects were communicating with it. You should check for nullptr, but it ‘may’ be okay if these objects still point to something deleted.

110

111 of 123

Another std::weak_ptr

  • Adapted from https://en.cppreference.com/w/cpp/memory/weak_ptr with some annotation as to what is going on
    • The motivation for weak_ptr is to ‘point’ to something that may exist, but if it does not, you are okay.
      • So the weak_ptr does not own the data in anyway, can only point to it if it exists.

111

112 of 123

std::auto_ptr - deprecated

  • You may also see this type of pointer on occasion, but it has been deprecated in c++ 17
    • Thus, don’t use it.

112

113 of 123

Conclusion

Wrapping up what we’ve learned

113

114 of 123

Conclusion -- C++ Programmers

  • Use Smart Pointers
    • Prefer std::unique_ptr to std::shared_ptr if you’re not sure
      • Some say std::shared_ptr may be a red flag that design could be improved (I’m guilty of this!)
      • You could also create a cycle with shared_ptr’s which is usually not desired behavior!
  • The examples today focused on memory safety, but smart pointers are useful for other resources
    • e.g. files open/close
      • Can specify custom deleters for these types of objects
  • When software scales (think even 10,000+ lines of code), that is when you really start to see the engineering benefits of smart pointers

114

115 of 123

Further resources and training materials

115

116 of 123

For educators and trainers, the ISOCPP Guidelines

116

117 of 123

Bonus if Time

117

118 of 123

  • Assembly wise, 3 instructions for creating each pointer here (px and u)
    • Would need to further investigate call times of new versus MakeUniq.

118

119 of 123

New in C++ 23 - out_ptr, inout_ptr

  • “out_ptr and inout_ptr are abstractions to bring both C APIs and smart pointers back into the promised land by creating a temporary pointer-to-pointer that updates (using a reset call or semantically equivalent behavior) the smart pointer when it destructs.” [paper proposal]
  • My understanding, is that this is for interop with C libraries, and having the ability to release ownership from say a std::unique pointer using out_ptr and inout_ptr is much cleaner design from this proxy object.

119

120 of 123

Introduction to Smart Pointers and Why

120

12:10-13:10, Tue, 6th September 2022

60 minutes | Introductory Audience

Thank you!

121 of 123

Thank you!

121

122 of 123

Extras and Notes

122

123 of 123

Talk Outline

  • Quick recap of the power of raw pointers
  • Some quick examples of common pointer pitfalls (dangling pointers, double frees, null pointer)
  • Example of wrapping a raw pointer to create a smart pointer class
  • Introduction and use case of unique_ptr
  • Introduction and use case of shared_ptr
  • Introduction and use case of weak_ptr
  • C++ 23 Smart Pointer Adaptors
  • Why you still may need raw pointers (C-library integration)

123