Exam 2 Study Guide
This guide consolidates what you need to know for Exam 2 (Summer 2026). Scope: Lectures 7, 8, 9, 10, and the HW#2 material. Each section lists the core concepts, common pitfalls, and where to read for more. Lectures 7 through 10 build directly toward HW#2 (a growable container built by hand), so the same ideas show up again in the HW#2 section at the end.
0. How to use this guide
Work through the sections in order. For each section: skim it, work the small
code examples in your head or on paper, then check the readings if anything is
fuzzy. After you have studied, try the Practice Problems to gauge your
understanding. Do not flip to the practice problems first; you will memorize
answers instead of learning concepts, and the exam questions will be different
anyway. The exam is on std::vector and the ideas behind it, so practicing
those ideas is practicing for the exam.
The practice problems teach concepts; the exam tests the same concepts with different code. Each practice problem is tagged with the concept it drills. The exam will not reuse those exact problems: it traces different code, uses different values or element types, or asks from a different angle (true/false, select-all, or "find the bug"). So memorizing a practice answer will not transfer, but understanding the concept behind it will. The practice set is in six sections (Lectures 7 to 10, then the Vector's behavior, then the Vector's internals); the last section's answers are released after the HW#2 due date, since they are about how to build the container you are still writing.
1. Lecture 7: Free store, pointers, destructors
How objects that outlive a single scope are created and destroyed, and who is responsible for cleaning them up.
Core concepts
The free store (heap). Local variables live on the stack and are destroyed automatically at the end of their scope. The free store is memory you allocate explicitly and must release explicitly.
int* p = new int(42); // allocate one int on the free store
// ... use *p ...
delete p; // release it
int* a = new int[5]; // allocate an array of 5 ints
// ... use a[i] ...
delete[] a; // release an array with delete[]
new pairs with delete; new[] pairs with delete[]. Mixing them (for
example delete on a new[] allocation) is undefined behavior.
Pointers. A pointer holds an address. nullptr is the "points at nothing"
value. Dereferencing (*p) reads or writes the pointed-to object; doing so on a
null or dangling pointer is undefined behavior.
Destructors. A destructor (~T()) runs when an object is destroyed: for a
local, at the end of its scope; for a free-store object, when you delete it.
Locals are destroyed in reverse order of construction. A destructor's job is to
release whatever the object owns.
struct Noisy {
Noisy() { std::cout << "ctor "; }
~Noisy() { std::cout << "dtor "; }
};
// { Noisy a; { Noisy b; } } prints: ctor ctor dtor ... (b dies at the inner brace)
Ownership. A raw owning pointer must be deleted exactly once, by whoever owns it. This is the seed of HW#2: a class that owns a free-store buffer allocates it in the constructor and frees it in the destructor.
class Vector {
double* elem_;
int sz_;
public:
Vector(int s) : elem_{new double[s]}, sz_{s} {} // acquire
~Vector() { delete[] elem_; } // release
};
This Vector is the course's central running example, and the direct lineage of
HW#2. The lecture stresses preferring a resource handle (a vector, string,
or smart pointer that owns the memory for you) over naked new and delete. Acquire the resource in the constructor and release it in the
destructor. (new throws std::bad_alloc if it cannot allocate; delete of a
nullptr is harmless.)
The simple model above uses new[]/delete[], which constructs and destroys all
the slots at once. A by-hand growable container goes one level deeper and
separates allocating storage from constructing elements: it gets raw,
uninitialized storage (::operator new), constructs elements into it only as they
are added (placement new), and on the way out destroys each live element before
releasing the storage (::operator delete). The idea to carry into the exam is
the pairing: storage is acquired once and freed once, and every element that was
constructed must be destroyed.
Common pitfalls
- Mixing
new/deletewithnew[]/delete[](undefined behavior). - Leak: allocating with
newand never callingdelete. - Double free: deleting the same object twice, often through two pointers that alias the same address.
- Dangling pointer / use-after-free: using a pointer after the object it points to has been deleted.
- Forgetting the destructor in a class that owns a resource (leak).
See: Tour §1.7 (pointers/arrays/refs), §5.2 (constructors/destructors, free-store Vector), §15.2 (pointers). PPP Ch. 15 (Vector and Free Store). learncpp 12.7 Introduction to pointers, 19.1 Dynamic allocation (new/delete), 15.4 Destructors.
2. Lecture 8: Arrays, pointers, references, C-style strings
The low-level building blocks: contiguous arrays, pointer arithmetic, references, and how C represents text.
Core concepts
C-style arrays. A fixed-size, contiguous block. Subscripting is defined as pointer arithmetic:
int a[4] = {10, 20, 30, 40};
a[i] == *(a + i); // true by definition
Array-to-pointer decay. When you pass an array to a function, it "decays" to
a pointer to its first element; the size is lost. You cannot copy one array into
another with =, and you cannot recover the length from a decayed pointer.
void f(int* p); // an array argument arrives as a pointer
int a[3] = {1,2,3};
f(a); // ok: decays to int*
int* p = a; // ok
int b[3] = a; // error: arrays are not copy-initializable
Pointer arithmetic. ++p advances a pointer by one element; p + i points
at the element i positions along. This is how you walk an array with a pointer.
References versus pointers. A reference is a nickname for an existing object: it must be initialized and can never be reseated. A pointer is a variable holding an address: it can be null and can be reseated.
int x = 1, y = 2;
int& r = x; r = y; // assigns y's value INTO x (r is x); cannot reseat
int* p = &x; p = &y; // reseats p to point at y; does not change any int
C-style strings. A C-string is a char array terminated by a null character
'\0'. A string literal like "hi" has type const char[] (here 3 chars: h,
i, \0) and lives in read-only storage. To get a string you can modify, copy
it into a char[]; for real text work, use std::string.
const char* s = "hello"; // points at read-only storage; do not write through it
char t[] = "hello"; // a writable copy; t[0] = 'H'; is fine
Alternatives to raw pointers (prefer these). The lecture spends real time on
safer replacements, and you should know what each is for: a std::span is "a
pointer that knows how many elements it points to" (the course's span is
range-checked); a std::array<T, N> is a fixed-size array whose size the
compiler knows and that does NOT silently decay to a pointer; and not_null is a
pointer checked never to be null. The guiding rule: work at the highest level you
can (vector, string, span, array), and drop to raw pointers and arrays
only to implement those facilities.
Common pitfalls
- Expecting an array to remember its size after it decays to a pointer.
- Trying to copy or assign arrays with
=. - Writing through a pointer to a string literal (undefined behavior).
- Forgetting the null terminator on a C-string.
- Confusing a reference (cannot reseat) with a pointer (can reseat, can be null).
See: Tour §1.7 (pointers/arrays/refs), §3.4 (reference parameters), §15.3-15.4 (array, span, alternatives). PPP Ch. 16 (Arrays, Pointers, and References). learncpp 12.3 Lvalue references, 17.8 C-style array decay, 17.9 Pointer arithmetic and subscripting, 17.10 C-style strings.
3. Lecture 9: Essential operations (copy, move, the rule of five)
How objects are copied, moved, and assigned, and what a class that owns a resource must do to behave correctly.
Core concepts
The essential operations. For any class, consider whether you need these (the compiler can generate them): a default constructor, a copy constructor, a copy assignment operator, a move constructor, a move assignment operator, and a destructor. (These six are also called the special member functions.)
Copy construction versus copy assignment. Initializing a new object from an
existing one is copy construction; assigning to an object that already exists
is copy assignment. The = in a declaration is initialization, not assignment.
std::string c = a; // copy CONSTRUCTOR (new object)
b = a; // copy ASSIGNMENT (b already exists)
Shallow versus deep copy. The compiler-generated copy copies each member as is. For a class that owns a raw pointer to a buffer, that means copying the pointer (shallow): now two objects share one buffer, which leads to aliasing bugs and a double free when both destructors run. A correct copy is a deep copy: allocate a new buffer and copy the elements into it.
The rule of zero and the rule of all. The course states it as two rules. The
rule of zero: if your class owns nothing raw (it holds only containers,
std::string, smart pointers, or primitives), define none of the essential
operations and let the compiler generate them. The rule of all: if you must
define any one of them (typically because you wrote a destructor to release a
resource), define them all. (This is the same idea widely known as the rule of
three or the rule of five.)
Move semantics. std::move(x) casts x to an rvalue so a move operation can
transfer its internals (its buffer) instead of copying them. The move
constructor "steals" the source's representation and then leaves the source as
the empty object (for the course Vector: it sets the source's size to 0 and its
pointer to nullptr). After a move, the source is in a valid but unspecified
state: you may assign to it or let it be destroyed, but you should not rely on its
value. Returning a local container moves (or the copy is elided), so it is cheap
and never deep-copies.
std::string b = std::move(a); // b steals a's buffer; a is left valid-but-empty
Changing size (capacity). A growable Vector tracks how many slots it has
allocated, which the course calls space (the standard library calls it the
vector's capacity), separately from its size. reserve(n) relocates the
elements into a new buffer with room for n; push_back calls reserve when
full, and the course's growth policy is to start at 8 and then double
(reserve(space == 0 ? 8 : 2 * space)). Relocating into a new buffer invalidates
any pointers or iterators into the old one.
Subscript operator and initializer-list constructor. Containers usually
provide both a non-const operator[] (returns T&, assignable) and a const one
(returns by value or const T&, read-only), plus an std::initializer_list
constructor so you can write Thing t = {1, 2, 3};.
Common pitfalls
- Relying on the compiler-generated copy for a class that owns a raw pointer: shallow copy leads to a double free.
- Writing a destructor but forgetting the copy and move operations (you have taken on the rule of five whether you meant to or not).
- Reading a moved-from object's value (it is unspecified).
- Forgetting the
constoverload ofoperator[], so const objects cannot be indexed.
See: Tour Ch. 6 (Essential Operations): §6.2 copy/move, §6.3 resource management, §6.4 conventional operations; §5.2.2 (subscripting, initializer-list constructor); §16.6 (move/forward). PPP Ch. 17 (Essential Operations). learncpp 14.14 Copy constructor, 21.12 Copy assignment, 21.13 Shallow vs. deep copying, 22.3 Move constructors and assignment, 21.9 Subscript operator.
4. Lecture 10: Templates and exceptions
Writing code that works for many types, and handling errors without leaking resources.
Core concepts
Function templates. A template lets one function work for many types. The compiler deduces the type argument from the call.
template <typename T>
T max_of(T a, T b) { return a > b ? a : b; }
max_of(3, 4); // T = int
max_of(3, 4.0); // error: T cannot be both int and double; write max_of<double>(3, 4.0)
Class templates. A class can be a template too. Since C++17 the compiler can often deduce the type arguments from the constructor call (class template argument deduction, CTAD).
template <typename T> class Box { T v_; public: Box(T v) : v_{v} {} };
Box<int> bi {5}; // explicit
Box bd {3.14}; // CTAD deduces Box<double>
Template definitions normally live in headers, because the definition must be visible wherever the template is used. Templates are the basis of generic programming (also called parametric polymorphism): one definition that works for many types, resolved at compile time. This contrasts with the run-time resolution of virtual functions in object-oriented programming.
Concepts (constraining template parameters). A concept is a compile-time
predicate that states what a template requires of its type argument. Instead of
template <typename T> you can write template <Element T> (or
template <typename T> requires Element<T>) so the compiler checks the
requirement up front and gives clearer errors. Useful standard concepts include
copyable, equality_comparable, integral, floating_point, and regular
(a "pretty normal" type that can be copied, moved, swapped, and compared).
Exceptions. Signal an error by throw; handle it with try/catch. As the
exception propagates, the stack unwinds and destructors of local objects run,
which is what makes resource cleanup reliable. Catch by const&.
if (b == 0) throw std::out_of_range("empty");
try { risky(); }
catch (const std::out_of_range& e) { /* handle */ }
For a container, this is why element access comes in two forms: at(i) does a
range check and throws std::out_of_range on a bad index, while operator[](i)
is unchecked and is undefined behavior out of range. The course Vector and the
standard vector both follow this: at is checked, [] is fast and unchecked.
RAII (Resource Acquisition Is Initialization), also called scoped resource
management. Tie a resource's lifetime to an object: acquire in the constructor,
release in the destructor. Because destructors run during stack unwinding, RAII
objects clean up even when an exception is thrown. The alternative, scattering
try/catch blocks to free raw resources by hand, does not scale: with two or
three raw resources and a few exit paths it is nearly impossible to get right.
Let RAII objects (a Vector, a unique_ptr) do the releasing.
Smart pointers. std::unique_ptr<T> is a single owner: it frees the object
automatically and is move-only (it cannot be copied). std::shared_ptr<T> is a
shared owner: copying it adds a co-owner, and the object is freed when the last
owner is destroyed (reference counting).
auto a = std::make_unique<int>(1);
auto b = std::move(a); // ownership transferred; a is now null
auto c = std::make_shared<int>(2);
auto d = c; // c and d share ownership
Common pitfalls
- Putting a template's definition in a
.cppfile (link errors at instantiation). - Catching an exception by value (can slice) instead of by
const&. - Managing a raw resource by hand where RAII would free it automatically, then leaking it when an exception is thrown.
- Trying to copy a
std::unique_ptr(its copy is deleted; usestd::move).
See: Tour Ch. 7 (Templates), §4.2 (exceptions), Ch. 6 (resource management / RAII), §15.2 (unique_ptr / shared_ptr). PPP Ch. 18 (Templates and Exceptions). learncpp 11.6 Function templates, 13.13 Class templates, 27.2 Basic exception handling, 22.5 std::unique_ptr, 22.6 std::shared_ptr.
5. HW#2 topics (cross-reference)
HW#2 asks you to build a growable container by hand: a std::vector-like class
that manages its own free-store buffer. It exercises Lectures 7 through 10 in one
project, which is why the exam draws on it heavily. You do not need to recite your
own implementation; you need to understand the behavior and the reasons behind it.
The HW#2 ideas most likely to appear on the exam:
- Free-store ownership (L7). The container owns a buffer on the free store (raw storage it allocates, then constructs elements into), reached through a pointer member, and frees it in the destructor. Exactly one owner; freed exactly once; every constructed element destroyed before the storage is released.
- Buffer and element access (L8). Elements live contiguously in the buffer; indexing is pointer arithmetic on that buffer.
- The rule of five (L9). Because the container owns raw memory, it needs a deep-copy copy constructor and copy assignment, a move constructor and move assignment that transfer the buffer, and a destructor. A subscript operator and an initializer-list constructor round out the interface.
- Templates and exceptions (L10). The container is a template, so it works for
any element type; out-of-range access reports an error (for example
std::out_of_range); RAII keeps it leak-safe.
Behavior to understand (these are what the practice problems drill, on
std::vector, which behaves the same way):
- Size versus capacity. Size is how many elements you have; capacity is how
many fit before the buffer must grow.
reserveraises capacity without changing size;resizechanges size, creating or destroying elements.shrink_to_fitlowers capacity toward size (a reallocation). - Geometric growth and invalidation. When the buffer is full, adding an element allocates a larger buffer, moves the existing elements over, and frees the old one. Growth is geometric (for example doubling), so adding many elements causes only a few reallocations. Any pointer or iterator taken before a reallocation is then invalidated.
- Deep copy and move. Copying the container duplicates its elements into separate storage (independent objects); moving it transfers the buffer and leaves the source valid but empty. A correct container also supports move-only element types.
- The standard operations.
operator[]is unchecked whileatthrowsstd::out_of_range;==is true when two vectors have the same size and equal elements;swapexchanges two vectors' contents in constant time;clearandpop_backreduce size but keep capacity;eraseremoves an element and shifts the later ones down. Iterators are positions frombegin()toend()(one past the last), which a range-for walks. The HW#2 driver functions read or copy through this public interface, with value semantics (for examplesorted_copyreturns a sorted copy and leaves its input unchanged).
Common exam pitfalls (HW#2)
- Saying the compiler-generated copy is fine for a buffer-owning class (it is a shallow copy and double-frees).
- Forgetting that a reallocation invalidates outstanding pointers and iterators.
- Confusing size with capacity, or assuming every insertion reallocates.
- Forgetting that the container must free its buffer exactly once (leak or double free otherwise).
See: the readings for Lectures 7 through 10 above. Tour Ch. 5-7 and §5.2 (the free-store Vector) are the closest single thread through this material. PPP Ch. 15-18.
How the exam is structured (procedural)
- Format: true/false, multiple choice, and select all that apply.
- No fill-in-the-blank and no open-ended coded responses.
- The topics in this guide are the full scope.
- Weighting: 70 to 80 percent of Exam 2 is HW#2 (the Vector) or closely related material, namely the free store and ownership from Lecture 7 and the essential operations from Lecture 9. The remaining 20 to 30 percent is the broader Lecture 8 and Lecture 10 foundations: arrays, pointers, and C-strings, then templates and exceptions. Study the Vector material hardest.
- The exam asks the same concepts as the practice set, but through different questions: different code, different values or element types, and different angles. Understanding each concept is what transfers, not the specific answer to a practice problem.
- Exam 2 is not cumulative; its scope is Lectures 7 to 10 and HW#2. The final exam is cumulative: it covers Exam 1 and Exam 2 material, and may also ask about topics within this scope that did not appear on Exam 2. Keep this guide for the final.
What to do the night before
- Skim each section of this guide once more, especially the rule of five (Section 3) and the free-store / ownership ideas (Sections 1 and 5).
- Work the practice problems if you have not, and re-read the explanations for any you missed.
- Sleep.