Skip to content

CT 301 Final - Practice Questions for Exam

CT 301: C++ Fundamentals · Summer 2026 · Dr. Francisco R. Ortega

Practice Questions for Exam

Practice for the final. Trace each snippet, decide your answer, then reveal it.
How to use this. Each question shows a short piece of code. Work out what it does on your own first, then click Show answer. Do not memorize the questions - learn the concept behind each one. The exam will use different code and may test other aspects of the same idea. Do this set after you have done the largest part of your studying.

Homework 1 concepts (the inventory system)

Trace each snippet against the HW#1 classes (Item, Equipment/Consumable/Borrowed, Inventory, Date).

1Trace an item's capabilities: pure virtual contract + defaulted overrides

Trace this program's output: (select all that apply)

Equipment e("E1","Rig","compute", 1000.0, Date{2026,1,1},
            "Dell","XPS","i7", /*obsolete*/false, /*broken*/true,
            Date{2027,1,1}, Date{2027,1,1});
std::cout << e.canLoan() << e.canSell() << e.currentValue();
  1. prints 010: broken ⇒ canLoan() is false; canSell() is still the default true; a broken unit's currentValue() is 0
  2. Item is abstract (canLoan/currentValue/attribute/printTo are pure virtual), so Item x{...}; would not compile
  3. prints 111
  4. canSell() is false because the unit is broken
Show answer

Answer: A, B

  • prints 010: broken ⇒ canLoan() is false; canSell() is still the default true; a broken unit's currentValue() is 0
  • Item is abstract (canLoan/currentValue/attribute/printTo are pure virtual), so Item x{...}; would not compile
2Virtual dispatch of operator<< through a base reference

What does os contain? (select all that apply)

std::unique_ptr<Item> p =
    std::make_unique<Consumable>("C1","Cable","supply", 5.0, 10, "Belkin","M");
std::ostringstream os;
os << *p;                // Item& -> which printTo?
  1. os gets Consumable::printTo output (dynamic dispatch through Item&): it contains CONSUMABLE and value=50.00
  2. one non-member operator<<(ostream&, const Item&) serves every item type
  3. os gets Item::printTo output (there is none — printTo is pure virtual)
  4. compile error, because p is declared Item, not Consumable
Show answer

Answer: A, B

  • os gets Consumable::printTo output (dynamic dispatch through Item&): it contains CONSUMABLE and value=50.00
  • one non-member operator<<(ostream&, const Item&) serves every item type
3Selling a Borrowed item: it is not ours

Trace the output: (select all that apply)

Inventory inv;
inv.add(std::make_unique<Borrowed>("B1","Scope","instr", 999.0,
        "Physics", Date{2026,12,1}, "Tek","T1"));
auto rejects = inv.sell(inv.select_all());
std::cout << rejects.size() << " " << inv.revenue();
  1. prints 1 0: a borrowed item is not ours to sell, so it is rejected and revenue stays 0
  2. sell returns the items it could not sell
  3. prints 0 999: the item sells for its value
  4. the borrowed item moves to State::Sold
Show answer

Answer: A, B

  • prints 1 0: a borrowed item is not ours to sell, so it is rejected and revenue stays 0
  • sell returns the items it could not sell
4Equipment depreciation: 20%/yr, floored at 10% of base

The code's reference "today" is 2026-06-01. What prints? (select all that apply)

// bought 2021-01-01, base value 1000, not broken
Equipment e("E1","Rig","compute", 1000.0, Date{2021,1,1},
            "Dell","T","i9", false, false, Date{2030,1,1}, Date{2030,1,1});
std::cout << e.currentValue();
  1. prints 100: 5 years at 20%/yr wipes the value past the 10% floor, so it clamps to baseValue*0.10
  2. the value never drops below 10% of the base value
  3. prints 0: 5 × 20% = 100% depreciation
  4. prints 1000: the unit is not broken
Show answer

Answer: A, B

  • prints 100: 5 years at 20%/yr wipes the value past the 10% floor, so it clamps to baseValue*0.10
  • the value never drops below 10% of the base value
5Loaning a Consumable: not loanable

Trace the output: (select all that apply)

Inventory inv;
inv.add(std::make_unique<Consumable>("C1","Ink","supply", 4.0, 25, "HP","M"));
auto rejects = inv.loan(inv.select_all(), "Alice", Date{2026,6,1});
std::cout << rejects.size();
  1. prints 1: Consumable::canLoan() is false, so loan rejects it
  2. loan returns the items it could not loan
  3. prints 0: the consumable is loaned to Alice
  4. a consumable becomes OnLoan like equipment
Show answer

Answer: A, B

  • prints 1: Consumable::canLoan() is false, so loan rejects it
  • loan returns the items it could not loan
6A state transition moves ownership between the per-state vectors

Trace the two counts printed: (select all that apply)

Inventory inv;
inv.add(std::make_unique<Equipment>("E1","Cam","av", 500.0, Date{2026,1,1},
        "Sony","A","x", false, false, Date{2027,1,1}, Date{2027,1,1}));
inv.loan(inv.select_all(), "Bob", Date{2026,6,1});
auto c = inv.count_by_state();
std::cout << c[State::InStock] << c[State::OnLoan];
  1. prints 01: the equipment moves from in-stock to on-loan
  2. changing state moves the owning unique_ptr between the inventory's per-state vectors
  3. prints 10: loaning does not change the item's state
  4. prints 11: the item is counted in two states at once
Show answer

Answer: A, B

  • prints 01: the equipment moves from in-stock to on-loan
  • changing state moves the owning unique_ptr between the inventory's per-state vectors
7Partial success: an action collects the items it cannot process

One sellable Equipment, one Borrowed. What prints? (select all that apply)

Inventory inv;
inv.add(std::make_unique<Equipment>("E1",...,false,false,...));  // sellable
inv.add(std::make_unique<Borrowed>("B1",...));                // not sellable
auto rej = inv.sell(inv.select_all());
std::cout << rej.size() << " " << inv.count_by_state()[State::Sold];
  1. prints 1 1: only the equipment sells; the borrowed item comes back in the reject list
  2. sell processes each item independently and collects the ones it cannot sell
  3. prints 0 2: both items sell
  4. prints 2 0: the whole call fails because one item is not sellable
Show answer

Answer: A, B

  • prints 1 1: only the equipment sells; the borrowed item comes back in the reject list
  • sell processes each item independently and collects the ones it cannot sell
8Date comparisons and zero-padded stream output

Trace the printed characters and os.str(): (select all that apply)

std::cout << (Date{2026,1,5} < Date{2026,2,1})
          << (Date{2026,6,1} < Date{2026,6,1});
std::ostringstream os; os << Date{2026,6,1};
  1. the two comparisons print 10 (Jan 5 is before Feb 1; a date is not < itself)
  2. os.str() is "2026-06-01" (zero-padded YYYY-MM-DD)
  3. the comparisons print 11
  4. os.str() is "2026-6-1"
Show answer

Answer: A, B

  • the two comparisons print 10 (Jan 5 is before Feb 1; a date is not < itself)
  • os.str() is "2026-06-01" (zero-padded YYYY-MM-DD)
9attribute(key) returns std::optional; unknown key → nullopt

Trace the output: (select all that apply)

Equipment e("E1","Rig","c", 1000.0, Date{2026,1,1},
            "Dell","XPS","i7", false,false, Date{2027,1,1}, Date{2027,1,1});
auto brand = e.attribute("brand");    // std::optional<std::string>
auto miss  = e.attribute("weight");   // not a field
std::cout << brand.value() << " " << miss.has_value();
  1. prints Dell 0: a known key returns its value; an unknown key returns std::nullopt
  2. attribute returns std::optional<std::string>, so callers test has_value() / value()
  3. prints Dell 1: every key returns a value
  4. e.attribute("weight") throws because the key is missing
Show answer

Answer: A, B

  • prints Dell 0: a known key returns its value; an unknown key returns std::nullopt
  • attribute returns std::optional<std::string>, so callers test has_value() / value()
10Rule of Zero: a unique_ptr member makes the whole class move-only

Which lines compile? (select all that apply)

Inventory a;
a.add(std::make_unique<Equipment>("E1", ...));
Inventory b = a;              // (1)
Inventory c = std::move(a);   // (2)
  1. line (1) does not compile: copying the inventory would copy a unique_ptr, whose copy is deleted
  2. line (2) compiles: the inventory is movable (its vectors transfer), following the rule of zero
  3. both lines compile and b, c are independent deep copies
  4. line (2) also fails to compile, because a unique_ptr cannot be moved either
Show answer

Answer: A, B

  • line (1) does not compile: copying the inventory would copy a unique_ptr, whose copy is deleted
  • line (2) compiles: the inventory is movable (its vectors transfer), following the rule of zero
11Polymorphic aggregation: total_value sums the virtual currentValue()

One Equipment (value 1000), one Borrowed (base 999). What prints? (select all that apply)

Inventory inv;
inv.add(std::make_unique<Equipment>("E1","R","c", 1000.0, Date{2026,1,1},
        "D","T","i", false,false, Date{2027,1,1}, Date{2027,1,1}));
inv.add(std::make_unique<Borrowed>("B1","Scope","i", 999.0,
        "Physics", Date{2026,12,1}, "Tek","T1"));
std::cout << inv.total_value(inv.select_all());
  1. prints 1000: total_value sums each item's own currentValue(); the borrowed item contributes 0 despite its 999 base value
  2. the sum dispatches dynamically to each item type's currentValue()
  3. prints 1999: it adds the base values (1000 + 999)
  4. the borrowed item must be filtered out by hand to reach 1000
Show answer

Answer: A, B

  • prints 1000: total_value sums each item's own currentValue(); the borrowed item contributes 0 despite its 999 base value
  • the sum dispatches dynamically to each item type's currentValue()

Homework 2 concepts (the hand-built vector)

Trace each snippet against your ct::Vector and the free-store material from Lectures 7-10.

12RAII: the destructor frees the buffer at end of scope

Trace the output, and say whether it leaks: (select all that apply)

{
    ct::Vector<int> v;
    for (int i = 0; i < 1000; ++i) v.push_back(i);
    std::cout << v.size();
}                              // <- v goes out of scope here
  1. prints 1000, then v's destructor runs at the closing brace and frees the heap buffer (RAII) — no leak, no manual delete
  2. the destructor destroys the elements, then releases the storage
  3. the buffer leaks because nothing calls delete
  4. you must write delete v; yourself before the closing brace
Show answer

Answer: A, B

  • prints 1000, then v's destructor runs at the closing brace and frees the heap buffer (RAII) — no leak, no manual delete
  • the destructor destroys the elements, then releases the storage
13Allocation is separate from construction (capacity vs size)

Trace the output: (select all that apply)

ct::Vector<std::string> v;
v.reserve(10);
std::cout << v.size() << " " << (v.capacity() >= 10);
  1. prints 0 1: reserve obtains raw storage (capacity) without constructing any element
  2. this is why capacity can exceed size — ::operator new (bytes) is separate from placement new (construction)
  3. prints 10 1: reserve(10) constructs 10 empty strings
  4. reserve is the same as resize
Show answer

Answer: A, B

  • prints 0 1: reserve obtains raw storage (capacity) without constructing any element
  • this is why capacity can exceed size — ::operator new (bytes) is separate from placement new (construction)
14Array-to-pointer decay loses the length

On a 64-bit build, what prints? (select all that apply)

int a[4] = {10,20,30,40};
auto f = [](int* p){ return sizeof(p); };
std::cout << sizeof(a) << " " << f(a);
  1. prints 16 8: inside f the array has decayed to a pointer, so sizeof is the pointer size
  2. passing a to f decays it to int*, losing the length
  3. prints 16 16: the array keeps its size when passed
  4. int b[4]; b = a; would copy the array
Show answer

Answer: A, B

  • prints 16 8: inside f the array has decayed to a pointer, so sizeof is the pointer size
  • passing a to f decays it to int*, losing the length
15Writable char[] copy; p[i] == *(p+i)

Trace the output: (select all that apply)

char s[] = "hi";
s[0] = 'H';
std::cout << s << " " << (s[1] == *(s + 1));
  1. prints Hi 1: char s[] is a writable copy, and s[i] is exactly *(s+i)
  2. s is a private, writable copy of the characters, so assigning s[0] is fine
  3. s and the literal "hi" refer to the same bytes
  4. prints hi 1: you cannot modify s
Show answer

Answer: A, B

  • prints Hi 1: char s[] is a writable copy, and s[i] is exactly *(s+i)
  • s is a private, writable copy of the characters, so assigning s[0] is fine
16Deep copy: independent buffers

Trace the output: (select all that apply)

ct::Vector<int> a{1,2,3};
ct::Vector<int> b = a;      // copy
b[0] = 99;
std::cout << a[0] << " " << (a.data() != b.data());
  1. prints 1 1: the copy is deep — b has its own buffer, so changing b[0] leaves a untouched
  2. the copy constructor allocates a new buffer and copies the elements
  3. prints 99 1: a and b share the buffer
  4. prints 1 0: a and b point at the same storage
Show answer

Answer: A, B

  • prints 1 1: the copy is deep — b has its own buffer, so changing b[0] leaves a untouched
  • the copy constructor allocates a new buffer and copies the elements
17Move steals the buffer; std::move is only a cast

Trace the output: (select all that apply)

ct::Vector<int> a{1,2,3};
ct::Vector<int> b = std::move(a);
std::cout << b.size() << " " << a.size();
  1. prints 3 0: the move steals the buffer; the moved-from source is left valid and empty
  2. std::move(a) is only a cast to an rvalue reference; the move constructor does the transfer
  3. prints 3 3: move copies the elements, leaving a intact
  4. the moved-from a keeps {1,2,3} as a backup copy
Show answer

Answer: A, B

  • prints 3 0: the move steals the buffer; the moved-from source is left valid and empty
  • std::move(a) is only a cast to an rvalue reference; the move constructor does the transfer
18CTAD deduces the element type; count-and-value constructor

Trace the output: (select all that apply)

ct::Vector v{10, 20, 30};        // no <int>
ct::Vector<double> w(3, 1.5);      // count-and-value
std::cout << std::is_same_v<decltype(v), ct::Vector<int>>
          << " " << w.size() << " " << w[0];
  1. prints 1 3 1.5: CTAD deduces ct::Vector<int> from the braces; w is 3 copies of 1.5
  2. CTAD lets you omit <int> when the element type is deducible
  3. prints 0 3 1.5: v is ct::Vector<double>
  4. ct::Vector v{10,20,30}; fails to compile without <int>
Show answer

Answer: A, B

  • prints 1 3 1.5: CTAD deduces ct::Vector<int> from the braces; w is 3 copies of 1.5
  • CTAD lets you omit <int> when the element type is deducible
19Exceptions: at() throws; operator[] is unchecked

Trace the output: (select all that apply)

ct::Vector<int> v{1,2,3};
try { std::cout << v.at(9); }
catch (const std::out_of_range&) { std::cout << "oor"; }
std::cout << " " << v[0];
  1. prints oor 1: at(9) throws std::out_of_range; operator[] is unchecked (here v[0])
  2. reserve(n) with n > max_size() throws std::length_error (a different exception)
  3. prints 0 1: at(9) returns a default int
  4. operator[] also throws when the index is out of range
Show answer

Answer: A, B

  • prints oor 1: at(9) throws std::out_of_range; operator[] is unchecked (here v[0])
  • reserve(n) with n > max_size() throws std::length_error (a different exception)
20Growth policy: 0 → 8 → double

Trace the output: (select all that apply)

ct::Vector<int> v;                 // capacity 0
for (int i = 0; i < 9; ++i) v.push_back(i);
std::cout << v.size() << " " << v.capacity();
  1. prints 9 16: capacity grows 0 → 8 on the first push, then doubles to 16 when the 9th overflows 8
  2. growth is geometric, so many pushes cause only a few reallocations
  3. prints 9 9: capacity always equals size
  4. prints 9 32
Show answer

Answer: A, B

  • prints 9 16: capacity grows 0 → 8 on the first push, then doubles to 16 when the 9th overflows 8
  • growth is geometric, so many pushes cause only a few reallocations
21Copy has its own buffer; move steals

Trace the output: (select all that apply)

ct::Vector<int> a{1,2,3};
ct::Vector<int> c = a;               // copy: own buffer
ct::Vector<int> m = std::move(a);    // move: steal
std::cout << (c.data() != m.data()) << " " << a.size();
  1. prints 1 0: the copy has its own buffer; the move stole a's buffer, leaving a size 0
  2. copy allocates and copies each element; move takes pointer/size/capacity and nulls the source
  3. prints 0 3: c and m share a buffer and a is unchanged
  4. prints 1 3
Show answer

Answer: A, B

  • prints 1 0: the copy has its own buffer; the move stole a's buffer, leaving a size 0
  • copy allocates and copies each element; move takes pointer/size/capacity and nulls the source
22reserve (capacity only) vs resize (element count)

Trace the output: (select all that apply)

ct::Vector<int> v;
v.reserve(50);
std::size_t a = v.size();
v.resize(5);
std::cout << a << " " << v.size() << " " << (v.capacity() >= 50);
  1. prints 0 5 1: reserve raised capacity only (size still 0); resize(5) set the element count to 5
  2. reserve never changes size; resize creates or destroys elements
  3. prints 50 5 1: reserve(50) makes size 50
  4. prints 0 5 0: resize(5) shrank capacity below 50
Show answer

Answer: A, B

  • prints 0 5 1: reserve raised capacity only (size still 0); resize(5) set the element count to 5
  • reserve never changes size; resize creates or destroys elements
23Iterators are raw pointers; rbegin() is the last element

Trace the output: (select all that apply)

ct::Vector<int> v{5,6,7,8};
std::cout << (v.end() - v.begin()) << " "
          << *v.rbegin() << " "
          << std::is_same_v<ct::Vector<int>::iterator, int*>;
  1. prints 4 8 1: end()-begin() is the size; rbegin() refers to the last element; the iterator is int*
  2. iterators are raw pointers into the contiguous buffer
  3. prints 4 5 1: rbegin() refers to the first element
  4. prints 3 8 1: end() refers to the last element
Show answer

Answer: A, B

  • prints 4 8 1: end()-begin() is the size; rbegin() refers to the last element; the iterator is int*
  • iterators are raw pointers into the contiguous buffer
24reserve relocates by MOVE, not copy or memcpy

Trace the output: (select all that apply)

ct::Vector<std::string> v;
v.reserve(2);
v.push_back("a"); v.push_back("b");
v.reserve(8);                        // relocate the 2 strings
std::cout << v.size() << " " << (v.capacity() >= 8)
          << " " << v[0] << v[1];
  1. prints 2 1 ab: reserve allocates a bigger buffer, move-constructs the existing elements in, destroys the old ones, frees the old buffer
  2. elements are relocated with std::uninitialized_move_n, not memcpy or a copy
  3. prints 0 1: reserving discards the existing elements
  4. reserve(8) copies the strings, leaving extra copies alive
Show answer

Answer: A, B

  • prints 2 1 ab: reserve allocates a bigger buffer, move-constructs the existing elements in, destroys the old ones, frees the old buffer
  • elements are relocated with std::uninitialized_move_n, not memcpy or a copy
25insert shifts+rotates; emplace_back builds in place, returns void

Trace the output: (select all that apply)

ct::Vector<int> v{1, 2, 3};
v.insert(v.begin() + 1, 9);          // -> 1 9 2 3
v.emplace_back(4);                   // build in place at the end
std::cout << v[1] << " " << v.size();
  1. prints 9 5: insert shifts later elements right and puts 9 at index 1; emplace_back builds the new element in place
  2. single-element inserts append then std::rotate the value into place; emplace perfect-forwards its args
  3. prints 2 5: insert overwrites index 1
  4. emplace_back returns a reference you must capture
Show answer

Answer: A, B

  • prints 9 5: insert shifts later elements right and puts 9 at index 1; emplace_back builds the new element in place
  • single-element inserts append then std::rotate the value into place; emplace perfect-forwards its args
26The signature HW2 difference: Vector<int> v(5, 7) is count-and-value

Trace the output: (select all that apply)

ct::Vector<int> v(5, 7);             // which constructor?
std::cout << v.size() << " " << v[0];
  1. prints 5 7: (5, 7) resolves to the count-and-value constructor — 5 copies of 7
  2. the range constructor takes a concrete const_iterator (a pointer), so two ints cannot be mistaken for a range — no concepts or enable_if needed
  3. prints 2 5: (5, 7) is treated as a range [5, 7)
  4. this call is ambiguous and fails to compile
Show answer

Answer: A, B

  • prints 5 7: (5, 7) resolves to the count-and-value constructor — 5 copies of 7
  • the range constructor takes a concrete const_iterator (a pointer), so two ints cannot be mistaken for a range — no concepts or enable_if needed
27erase: shift down, size−1, returns an iterator

Trace the output: (select all that apply)

ct::Vector<int> v{10, 20, 30, 40};
auto it = v.erase(v.begin() + 1);    // remove 20
std::cout << v.size() << " " << v[1] << " " << *it;
  1. prints 3 30 30: erase moves later elements down one, shrinks size to 3, and returns an iterator to the element now at that spot (30)
  2. iterators at or after the erased position are invalidated
  3. prints 4 30 30: erase leaves the size unchanged
  4. prints 3 20 20: erase returns an iterator to the removed value
Show answer

Answer: A, B

  • prints 3 30 30: erase moves later elements down one, shrinks size to 3, and returns an iterator to the element now at that spot (30)
  • iterators at or after the erased position are invalidated
28shrink_to_fit lowers capacity to size; swap exchanges in O(1)

Trace the output: (select all that apply)

ct::Vector<int> a{1, 2, 3};
a.reserve(100);                     // capacity 100, size 3
a.shrink_to_fit();                 // capacity -> size (3)
ct::Vector<int> b{7, 8};
a.swap(b);                         // O(1) exchange
std::cout << a.capacity() << " " << a.size() << " " << a[0];
  1. prints 2 2 7: shrink_to_fit set a's capacity to 3, then swap exchanged a with b ({7,8}), so a[0] is 7
  2. shrink_to_fit lowers capacity toward size; swap just exchanges the pointer/size/capacity
  3. prints 3 3 1: swap does nothing to a
  4. shrink_to_fit raised a's capacity to match b
Show answer

Answer: A, B

  • prints 2 2 7: shrink_to_fit set a's capacity to 3, then swap exchanged a with b ({7,8}), so a[0] is 7
  • shrink_to_fit lowers capacity toward size; swap just exchanges the pointer/size/capacity
29Element access: front() returns T&; back(), data()

Trace the output: (select all that apply)

ct::Vector<int> v{10, 20, 30};
v.front() = 99;                     // front() returns int&
const ct::Vector<int>& r = v;
std::cout << v[0] << " " << v.back() << " " << (r.data() == v.data());
  1. prints 99 30 1: front() returns a non-const int&, so assigning through it changes v[0]; back() is the last element
  2. on a const vector, operator[]/front/back return const T& (read-only)
  3. prints 10 30 1: front() returns a copy, so the assignment is a no-op
  4. prints 99 10 1: back() returns the first element
Show answer

Answer: A, B

  • prints 99 30 1: front() returns a non-const int&, so assigning through it changes v[0]; back() is the last element
  • on a const vector, operator[]/front/back return const T& (read-only)
30Member type aliases (the using set)

Trace the four printed bits: (select all that apply)

using V = ct::Vector<double>;
std::cout << std::is_same_v<V::value_type, double>
          << std::is_same_v<V::iterator, double*>
          << std::is_same_v<V::const_reference, const double&>
          << std::is_same_v<V::size_type, std::size_t>;
  1. prints 1111: value_type=double, iterator=double*, const_reference=const double&, size_type=std::size_t
  2. these using aliases are what let generic code name the vector's associated types
  3. prints 1011: iterator is a special class type, not double*
  4. value_type is always int regardless of T
Show answer

Answer: A, B

  • prints 1111: value_type=double, iterator=double*, const_reference=const double&, size_type=std::size_t
  • these using aliases are what let generic code name the vector's associated types
31Move-only element types: Vector<unique_ptr<T>> is movable, not copyable

Trace the output: (select all that apply)

ct::Vector<std::unique_ptr<int>> v;
v.push_back(std::make_unique<int>(42));
ct::Vector<std::unique_ptr<int>> w = std::move(v);   // move the vector
std::cout << w.size() << " " << *w[0] << " " << v.size();
  1. prints 1 42 0: a vector of move-only elements can itself be moved; w takes the buffer, v is left empty
  2. you can store move-only types like unique_ptr and still move the whole vector
  3. ct::Vector<std::unique_ptr<int>> w = v; (a copy) would compile and duplicate the pointers
  4. prints 1 42 1: moving the vector copies the elements
Show answer

Answer: A, B

  • prints 1 42 0: a vector of move-only elements can itself be moved; w takes the buffer, v is left empty
  • you can store move-only types like unique_ptr and still move the whole vector