Skip to content

Exam 2 Study Guide

CT 301: C++ Fundamentals · Summer 2026
Exam 2 Study Guide
Scope: Lectures 7, 8, 9, 10 and HW#2 (ct::Vector). About 70-80 percent of Exam 2 is HW#2 or related material.

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

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

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

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

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:

Behavior to understand (these are what the practice problems drill, on std::vector, which behaves the same way):

Common exam pitfalls (HW#2)

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)

What to do the night before