CT 301 Exam 1 Practice Problems
Important: how to use this document
Practice them only after you have studied. Going through these before reviewing the material will not help you. You will memorize specific answers, the actual exam will use different problems on the same concepts, and your score will not reflect your understanding.
After you finish a section, check your answers in the Answer Key at the end. If you missed a question, return to the corresponding section of the Study Guide before trying more practice. Score yourself honestly: a wrong answer you understand after reading the rationale teaches you more than a lucky guess.
The exam uses four question types: true/false, multiple choice, select all that apply, and possibly matching (drag concepts from left to right on Canvas). This document uses the same four types in roughly the same proportion. There are no fill-in-the-blank or open-ended questions because the real exam does not have any.
Difficulty is labeled per problem: Easy (recall), Medium (apply concept), Hard (multiple concepts or subtle case).
Section 1: Lecture 1 (Hello World, Objects, Types, Values)
Problem 1.1 (Easy, True/False). In C++, int x = 3.7; compiles and assigns 3 to x.
Problem 1.2 (Easy, True/False). int x {3.7}; also compiles and assigns 3 to x.
Problem 1.3 (Medium, Multiple Choice). What is the value of i after auto i = 7 / 2;?
A. 3.5 B. 3 C. 4 D. The type of i is unclear, so this is a compile error.
Problem 1.4 (Medium, Multiple Choice). What is the type of x after auto x = std::string{"hi"};?
A. const char* B. char[3] C. std::string D. auto
Problem 1.5 (Easy, Select All That Apply). Which of the following are valid ways to initialize an int to 5?
A. int x = 5; B. int x(5); C. int x {5}; D. int x; then x = 5; E. auto x = 5;
Section 2: Lecture 2 (Functions, Scope, Constants, Pointers/References)
Problem 2.1 (Easy, True/False). A function declaration must include parameter names.
Problem 2.2 (Easy, True/False). A reference can be reseated to point to a different variable after it is initialized.
Problem 2.3 (Medium, Multiple Choice). Given const int* p;, which is true?
A. The pointer cannot be reseated. B. The value pointed to cannot be modified through p. C. Both the pointer and the pointed-to value are immutable. D. The pointer must be nullptr.
Problem 2.4 (Medium, Multiple Choice). Given int* const p = &x;, which is true?
A. The pointer cannot be reseated. B. The value pointed to cannot be modified through p. C. Both the pointer and the pointed-to value are immutable. D. The pointer must be nullptr.
Problem 2.5 (Hard, Select All That Apply). Which of the following are valid uses of constexpr?
A. constexpr int n = 10; B. constexpr int square(int x) { return x*x; } C. int n = get_input(); constexpr int doubled = n * 2; D. constexpr int x; E. constexpr int day = 7; int arr[day];
Problem 2.6 (Hard, True/False). A function that returns int& and returns a reference to a parameter passed in by reference is safe to use.
Section 3: Lecture 3 (User-Defined Types and Modularity)
Problem 3.1 (Easy, True/False). Inside a struct, members default to private access.
Problem 3.2 (Easy, Multiple Choice). Which of the following is the modern, type-safe enum?
A. enum Color { Red, Green, Blue }; B. enum class Color { Red, Green, Blue }; C. typedef enum { Red, Green, Blue } Color; D. #define RED 0
Problem 3.3 (Medium, Select All That Apply). Which of the following can be put inside a header file (.h) safely?
A. A class declaration. B. A non-inline function definition. C. A function declaration. D. using namespace std; E. A constexpr variable definition.
Problem 3.4 (Medium, True/False). Including the same header twice in the same .cpp file is safe as long as #pragma once (or a header guard) is at the top of the header.
Problem 3.5 (Easy, Multiple Choice). What is the purpose of #pragma once?
A. To make the compiler run faster. B. To prevent multiple inclusion of the same header in one translation unit. C. To force a single instance of an object at runtime. D. To define a singleton class.
Section 4: Lecture 5 (Functions in depth)
Problem 4.1 (Easy, True/False). A function may be declared multiple times in a program but must be defined exactly once.
Problem 4.2 (Medium, Multiple Choice). Which parameter passing mode is best for a large, read-only std::string?
A. void f(std::string s) B. void f(std::string& s) C. void f(const std::string& s) D. void f(std::string* s)
Problem 4.3 (Medium, Select All That Apply). Which of the following are valid function overloads of int abs(int x);?
A. double abs(double x); B. int abs(double x); C. unsigned abs(int x); D. int abs(int x, int y); E. int abs(int& x);
Problem 4.4 (Hard, Multiple Choice). Where should the default value for a parameter be declared?
A. In the function definition only. B. In both the declaration and the definition. C. In the declaration only. D. It does not matter, both work the same way.
Problem 4.5 (Hard, True/False). Returning a std::vector by value from a function is expensive because the entire vector is copied.
Problem 4.6 (Medium, Multiple Choice). Why is returning a reference to a local variable a bug?
A. It does not compile. B. The local is destroyed when the function returns; the returned reference is dangling. C. References cannot be returned from functions. D. The compiler always rejects this.
Section 5: Lecture 6 (Classes and class design)
Problem 5.1 (Easy, True/False). A class with a pure virtual function can be instantiated directly.
Problem 5.2 (Easy, Multiple Choice). What is the signature of a pure virtual function?
A. virtual void f(); B. void f() = 0; C. virtual void f() = 0; D. pure virtual void f();
Problem 5.3 (Medium, Multiple Choice). Why must a polymorphic base class have a virtual destructor?
A. So that derived classes can have destructors. B. So that delete base_ptr invokes the derived class destructor first, then the base class destructor. C. So that the class can be copied. D. Because the standard requires it for all classes.
Problem 5.4 (Medium, Select All That Apply). Given:
class Item {
public:
virtual std::string id() const = 0;
virtual ~Item() = default;
};
Which of the following are true?
A. Item is an abstract class. B. You can write Item it; to create an Item object. C. A derived class must override id() (or remain abstract itself). D. The destructor must be virtual because Item is used polymorphically. E. Item has no data members.
Problem 5.5 (Hard, Multiple Choice). Consider:
class Animal { public: virtual void speak() { std::cout << "?"; } };
class Dog : public Animal { public: void speak() override { std::cout << "Woof"; } };
Animal a = Dog{}; // Line A
a.speak(); // Line B
What does Line B print, and why?
A. Woof, because speak is virtual. B. ?, because of object slicing: a is an Animal, not a Dog. C. ?, because speak is not virtual. D. Compile error on Line A.
Problem 5.6 (Hard, Select All That Apply). Which of these are best done by declaring the function as a free function (not a member)?
A. operator== between two objects of the same class. B. operator<< for std::ostream output. C. operator+ between an int on the left and your class on the right. D. operator= (copy assignment). E. A print() member function.
Problem 5.7 (Medium, True/False). Marking an override with the override keyword is required by the standard; the program will not compile without it.
Section 6: HW#1 topics
Problem 6.1 (Easy, True/False). A std::unique_ptr can be copied to create a second owner of the same object.
Problem 6.2 (Easy, Multiple Choice). To put a std::unique_ptr into a std::vector, you must:
A. Copy it with vec.push_back(p); B. Move it with vec.push_back(std::move(p)); C. Dereference it with vec.push_back(*p); D. Convert it to a raw pointer first.
Problem 6.3 (Medium, Multiple Choice). In HW1, what does inv.select_by("brand", "Dell") return?
A. A std::vector of matching items, transferring ownership. B. A std::vector of non-owning views into items still owned by the Inventory. C. A std::vector of copies. D. A std::shared_ptr.
Problem 6.4 (Medium, Select All That Apply). Which of the following are valid in HW#1's design?
A. Calling new Equipment(...) directly in your code. B. Using std::make_unique to construct items. C. Storing raw Item* long-term in a separate class member. D. Returning a non-owning std::vector from a selection query. E. Writing a destructor for Inventory to free its items.
Problem 6.5 (Hard, True/False). A class that holds only std::vector, std::unique_ptr, std::string, and primitive members can rely on the compiler-synthesized destructor, copy operations, and move operations.
Problem 6.6 (Hard, Select All That Apply). Which of the following statements about std::move(p), where p is a std::unique_ptr, are true?
A. std::move(p) deletes the object owned by p. B. std::move(p) casts p to an rvalue reference; no actual move happens at that point. C. After moving p into a container, p is in a valid-but-empty state; dereferencing it would crash. D. After std::move(p), the variable p no longer exists in the program. E. std::move requires or .
Section 7: Matching
Problem 7.1 (Medium). Match each term on the left with its description on the right.
| Terms | Descriptions | ||
|---|---|---|---|
| A | unique_ptr | 1 | Holds an address; can be null; modern C++ uses this for non-owning views, not for ownership. |
| B | shared_ptr | 2 | Single owner of a heap object; uncopyable; transferred with std::move. |
| C | weak_ptr | 3 | Many owners with a reference count; object deleted when count reaches zero. |
| D | raw pointer | 4 | Non-owning observer of a shared_ptr-owned object; does not affect the reference count. |
| E | reference (T&) | 5 | Nickname for an existing variable; cannot be null; cannot be reseated. |
Problem 7.2 (Easy). Match each keyword/concept on the left with its purpose on the right.
| Terms | Descriptions | ||
|---|---|---|---|
| A | virtual | 1 | Promises the value will not be modified through this name. |
| B | override | 2 | Marks a member function for dynamic dispatch based on the actual object type. |
| C | const | 3 | Requires the value to be computable at compile time. |
| D | constexpr | 4 | Modern, scoped, type-safe enumeration; no implicit conversion to int. |
| E | enum class | 5 | Tells the compiler this member function intends to override a base class virtual; compile error if it does not. |
Problem 7.3 (Hard). Match each scenario on the left with the most appropriate parameter-passing mode on the right.
| Terms | Descriptions | ||
|---|---|---|---|
| A | Function reads a large std::string and returns its length. | 1 | By value: void f(int x); |
| B | Function modifies a caller's std::vector<int> in place. | 2 | By const reference: void f(const std::string& s); |
| C | Function takes a small int and returns its square. | 3 | By non-const reference: void f(std::vector<int>& v); |
| D | Function takes ownership of a std::unique_ptr<Item>. | 4 | By value, then move inside: void f(std::unique_ptr<Item> p); |
| E | Function takes an optional reference to an Item, allowing null. | 5 | By raw pointer (may be null): void f(Item* p); |
Section 8: Code comprehension (harder)
These problems require you to read code carefully and identify subtle correctness issues. Surface pattern-matching will lead you to the wrong answer. Some use HW1 directly; others use a parallel problem (a Library, a Garage, etc.) so that what you really need is the concept, not your memory of HW1's code.
All problems in this section are Hard by default. Take your time on each one.
Problem 8.1 (Hard, Multiple Choice). Consider this Library design, a parallel to HW1's Item hierarchy:
class Book {
public:
virtual std::string title() const { return ""; }
virtual double lateFee() const { return 0.50; }
~Book() = default; // (A)
};
class RareBook : public Book {
std::string title_;
std::unique_ptr<std::vector<int>> pages_;
public:
RareBook(std::string t) : title_(std::move(t)), pages_(std::make_unique<std::vector<int>>(500)) {}
std::string title() const override { return title_; }
double lateFee() const override { return 25.00; }
};
void process(std::vector<std::unique_ptr<Book>>& books) {
for (auto& b : books) {
std::cout << b->title() << " " << b->lateFee() << "\n";
}
books.clear(); // (B)
}
After process runs on a vector containing 10 RareBook objects, what is the most accurate statement?
A. The output is correct, and all memory is properly released. B. The output is correct, but each RareBook's 500-int vector leaks because Book does not have a virtual destructor. C. The output is wrong because lateFee is not actually overridden; line (A) prevents virtual dispatch. D. The code does not compile because RareBook::title() returns by value but the base returns by value as well (no covariance issue).
Problem 8.2 (Hard, Select All That Apply). Consider this Clinic class, parallel to (but different from) HW1's Inventory:
class Clinic {
std::vector<std::unique_ptr<Patient>> active_;
public:
std::vector<Patient*> select_with(const std::string& condition) {
std::vector<Patient*> result;
for (auto& p : active_) {
if (p->hasCondition(condition)) {
result.push_back(p.get());
}
}
return result;
}
void discharge(const std::vector<Patient*>& targets) {
for (Patient* t : targets) {
auto it = std::find_if(active_.begin(), active_.end(),
[t](const std::unique_ptr<Patient>& p) { return p.get() == t; });
if (it != active_.end()) {
active_.erase(it); // (X)
}
}
}
};
// caller code:
Clinic c;
// ... c has 5 patients with the flu ...
auto sick = c.select_with("flu");
c.discharge(sick);
for (Patient* s : sick) {
std::cout << s->name() << "\n"; // (Y)
}
Which of the following statements are true?
A. Line (X) destroys the Patient objects, since active_.erase(it) runs the unique_ptr's destructor. B. After c.discharge(sick) returns, every pointer in sick is a dangling raw pointer. C. AddressSanitizer would flag line (Y) as a heap-use-after-free. D. Line (X) is correct because the patients are being discharged; they should be destroyed. E. The bug would not be caught by a normal compile; it requires runtime sanitizer or careful testing to find. F. A fix is to move each unique_ptr into a separate discharged_ vector before erasing from active_, so the patient objects are not destroyed.
Problem 8.3 (Hard, Multiple Choice). A Garage tracks vehicles. The following code compiles and runs without sanitizer errors, but it has a subtle correctness bug:
class Vehicle {
public:
virtual std::string describe() const { return "vehicle"; }
virtual ~Vehicle() = default;
};
class Truck : public Vehicle {
int payload_kg_;
public:
Truck(int p) : payload_kg_(p) {}
std::string describe() { return "truck " + std::to_string(payload_kg_); } // (X)
};
void print_each(const std::vector<std::unique_ptr<Vehicle>>& vs) {
for (const auto& v : vs) {
std::cout << v->describe() << "\n";
}
}
int main() {
std::vector<std::unique_ptr<Vehicle>> vs;
vs.push_back(std::make_unique<Truck>(5000));
print_each(vs);
}
What does the program print, and why?
A. truck 5000, because virtual dispatch finds Truck::describe(). B. vehicle, because Truck::describe() on line (X) is not const, so it does not override the base's const version; dispatch goes to Vehicle::describe(). C. truck 5000, because the compiler-generated covariant override binds to Truck::describe() regardless of const. D. The program does not compile because of the missing override keyword.
Problem 8.4 (Hard, Select All That Apply). Two implementations of the same helper. Which statements are true?
// Version A
std::string& greet(std::string& name) {
name = "Hello, " + name;
return name;
}
// Version B
std::string& greet_v2() {
std::string s = "Hello";
return s;
}
// Version C
std::string greet_v3() {
std::string s = "Hello";
return s;
}
A. Version A is safe: the caller's name outlives the function, so the returned reference is valid. B. Version B has a dangling reference: s is destroyed at function return. C. Version C is slow because the entire string is copied on return. D. Version C is essentially free at runtime due to NRVO or implicit move on return. E. Version B will reliably crash. The compiler always rejects this code. F. Version A modifies the caller's name in place; using the return value is optional but useful for chaining.
Problem 8.5 (Hard, Multiple Choice). In an HW1-like setting:
class Item {
public:
virtual std::string id() const = 0;
virtual bool canLoan() const { return true; }
virtual ~Item() = default;
};
class Consumable : public Item {
std::string id_;
public:
Consumable(std::string i) : id_(std::move(i)) {}
std::string id() const override { return id_; }
bool canLoan() override { return false; } // (X)
};
int main() {
std::unique_ptr<Item> p = std::make_unique<Consumable>("CXY-1");
std::cout << p->canLoan() << "\n"; // (Y)
}
What does line (Y) print, and what is the underlying reason?
A. 0 (false). Consumable::canLoan overrides the base implementation; virtual dispatch finds it. B. 1 (true). Line (X) is missing const, so it does not override the base's const canLoan(). The base's implementation runs. C. The code does not compile because canLoan is missing override on line (X). D. 0 (false). The derived class's canLoan shadows the base's; static dispatch always picks the derived one.
Problem 8.6 (Hard, Select All That Apply). Consider a Hospital tracking Patients. The following code aims to track and process patients:
class Patient {
public:
Patient(std::string n) : name_(std::move(n)) {}
std::string name_;
};
class Hospital {
std::vector<std::unique_ptr<Patient>> active_;
std::vector<std::unique_ptr<Patient>> discharged_;
public:
void admit(std::unique_ptr<Patient> p) {
active_.push_back(std::move(p));
}
void discharge(Patient* p) { // (X)
auto it = std::find_if(active_.begin(), active_.end(),
[p](const std::unique_ptr<Patient>& up) { return up.get() == p; });
if (it == active_.end()) return;
discharged_.push_back(std::move(*it)); // (Y)
active_.erase(it); // (Z)
}
Patient* find(const std::string& name) {
for (auto& up : active_) {
if (up->name_ == name) return up.get();
}
return nullptr;
}
};
int main() {
Hospital h;
h.admit(std::make_unique<Patient>("Alice"));
h.admit(std::make_unique<Patient>("Bob"));
Patient* alice = h.find("Alice");
h.discharge(alice);
std::cout << alice->name_ << "\n"; // (W)
}
Which of the following statements are true?
A. Line (W) is undefined behavior: after discharge, alice is a dangling pointer because the Patient was destroyed. B. Line (W) is safe: the Patient was moved (not destroyed) from active_ to discharged_, and alice still points to the same Patient object on the heap. C. Line (Z) destroys the Patient because erase calls the unique_ptr's destructor. D. After line (Y), the unique_ptr at position it is empty; line (Z) only erases the empty slot, not the patient. E. AddressSanitizer would flag line (W) as a heap-use-after-free. F. The bug class here is the same as Problem 8.2 (raw pointer used across an ownership-affecting operation).
Important. If you missed a problem, re-read the cross-referenced section of the Study Guide before trying more problems. The point is not to score 100% on this document; the point is to find what you do not yet understand and fix it.
Section 1 (Lecture 1)
1.1. True. Old-style = initialization allows narrowing; 3.7 is truncated to 3. (Study Guide §1)
1.2. False. Brace initialization rejects narrowing; this is a compile error. (Study Guide §1)
1.3. B (3). Both operands are int, so integer division applies. (Study Guide §1)
1.4. C (std::string). auto deduces the type of the initializer. (Study Guide §1)
1.5. A, B, C, D, E. All five are valid ways to set x to 5. Brace {5} is preferred in modern code because it catches narrowing in cases where the source type is wider. (Study Guide §1)
Section 2 (Lecture 2)
2.1. False. Parameter names are optional in declarations; only types are required. int add(int, int); is a valid declaration. (Study Guide §2)
2.2. False. A reference is bound at initialization and cannot be reseated. Assigning to a reference modifies the referent, not the reference itself. (Study Guide §2; Pointers Guide §4)
2.3. B. const int* is a pointer to a const int. You cannot modify what p points to, but p itself can be reseated. (Study Guide §2)
2.4. A. int* const is a const pointer to int. The pointer cannot be reseated, but you can modify what it points to. (Study Guide §2)
2.5. A, B, E. A and B are textbook uses. E is valid because day is a compile-time constant. C is invalid: get_input() is not constexpr. D is invalid: constexpr requires an initializer. (Study Guide §2)
2.6. True. The parameter outlives the function call (it is the caller's), so a reference to it remains valid after the function returns. This is the trimAll pattern. (Study Guide §2 + §4; Pointers Guide §5)
Section 3 (Lecture 3)
3.1. False. struct members default to public access; class members default to private. (Study Guide §3)
3.2. B (enum class). Modern, scoped, no implicit conversion to int. (Study Guide §3)
3.3. A, C, E. Class declarations, function declarations, and constexpr variables are safe in headers. B causes multiple-definition link errors unless marked inline. D pollutes every translation unit that includes the header. (Study Guide §3, §4)
3.4. True. #pragma once (or include guards) makes the second include a no-op in the same translation unit. (Study Guide §3)
3.5. B. Prevents multiple inclusion of the same header in one translation unit. (Study Guide §3)
Section 4 (Lecture 5)
4.1. True. ODR (One Definition Rule). Declarations can be repeated; the definition appears once. (Study Guide §4)
4.2. C (const std::string&). Passes by reference (no copy) and the function cannot modify the caller's string. (Study Guide §4)
4.3. A, D, E. A and D differ in parameter types and arity. E differs in reference vs value parameter (note: not always best practice, but technically a valid overload). B fails: return type alone cannot distinguish overloads. C is similar; return type and a signed/unsigned parameter difference is the same parameter type for overloading purposes (the parameter is int in both, so this would clash). (Study Guide §4)
4.4. C. Defaults must appear in the declaration the caller sees (typically the header). Putting them in the definition only would hide them from callers. (Study Guide §4)
4.5. False. NRVO (Named Return Value Optimization) and implicit move on return make this essentially free. The returned vector is constructed directly in the caller's storage, or moved (constant-time) if NRVO does not apply. (Study Guide §4; Pointers Guide §5)
4.6. B. The local is destroyed at end of scope; the returned reference is dangling. The code compiles (the compiler usually warns), but using the returned reference is undefined behavior. (Study Guide §4; Pointers Guide §5)
Section 5 (Lecture 6)
5.1. False. A class with at least one pure virtual function is abstract and cannot be instantiated. Only derived classes that override all pure virtuals can. (Study Guide §5)
5.2. C. virtual void f() = 0; declares a pure virtual function. (Study Guide §5)
5.3. B. Without a virtual destructor, delete base_ptr only runs the base class destructor, leaving derived-class members un-destroyed (resource leak). (Study Guide §5)
5.4. A, C, D, E. Item is abstract because it has a pure virtual function (A); only derived classes that override all pure virtuals can be instantiated (B is wrong). Derived classes must override id() or remain abstract themselves (C). The destructor is virtual because Item is used polymorphically through unique_ptr (D). The snippet shown has no data members (E). (Study Guide §5)
5.5. B. Object slicing: copying a Dog into an Animal value drops the Dog-specific parts, including its overridden speak. Even though speak is virtual, the actual object is now an Animal. (Study Guide §5)
5.6. B, C. operator<< for std::ostream must be a free function because the left operand is the stream, not your class. operator+ between int and your class also must be free (or member of int, which is impossible). A and D are commonly members; E is a member by design. (Study Guide §5)
5.7. False. override is recommended but not required. Without it, a typo in the signature silently creates a new function instead of overriding (the bug override would catch). (Study Guide §5)
Section 6 (HW#1)
6.1. False. unique_ptr cannot be copied; that would create a second owner, which violates the "unique" guarantee. (Study Guide §6; Pointers Guide §9)
6.2. B (std::move(p)). unique_ptr can only be moved into the vector, not copied. (Study Guide §6; Pointers Guide §9)
6.3. B. std::vector, a non-owning view. The Inventory still owns the items. (Study Guide §6; Pointers Guide §15)
6.4. B, D. B (make_unique) and D (returning non-owning Item* views from selections) are exactly the HW#1 design. A is penalized (-20). C is dangerous: raw pointers stored across operations can dangle. E is unnecessary because of Rule of Zero. (Study Guide §6; Pointers Guide §14, §15)
6.5. True. Rule of Zero: when all members manage their own resources (containers, smart pointers, strings, primitives), the compiler-synthesized special members do the right thing. (Study Guide §6; Pointers Guide §14)
6.6. B, C, E. B is the precise statement of what std::move does (a cast to rvalue reference; the actual move happens during the subsequent operation). C is the standard's "valid but unspecified" state. E is correct: std::move lives in (and transitively includes it). A is wrong (move does not delete, the new owner takes over). D is wrong (the variable still exists; it is just empty). (Study Guide §6; Pointers Guide §10)
Section 7 (Matching)
7.1.
7.2.
7.3.
Section 8 (Code comprehension)
8.1. B. Line (A) declares the destructor as non-virtual (just ~Book() = default;, missing virtual). When books.clear() (line B) runs, the unique_ptr calls delete through the base pointer. Without a virtual destructor, only ~Book runs; ~RareBook does not, so the pages_ unique_ptr is never destroyed, leaking its 500-int vector. Common trap: the code "looks right" because title() and lateFee() use virtual and override; the destructor is the missing piece. C is wrong because line (A) is about the destructor, not virtual dispatch on member functions. D is wrong because the return types match. (Study Guide §5; Pointers Guide §14)
8.2. A, B, C, E, F. Line (X) calls active_.erase(it), which destroys the unique_ptr at that position; destroying the unique_ptr deletes the owned Patient. So A is true. Every Patient in sick is now a raw pointer to freed memory: B. Line (Y) dereferences a dangling pointer; ASan would report heap-use-after-free: C. D is wrong: "discharge" should move patients out of active care, not destroy them; the implementation shown is buggy. E is true: the compile succeeds; only a runtime sanitizer or careful testing catches it. F describes a correct fix: if you std::move(it) into a separate vector first, then erase(it) only removes an empty unique_ptr slot (a no-op for the patient object), so the raw pointers in sick would still be valid. This is the lesson from Problem 8.6's correct version. (Study Guide §6; Pointers Guide Appendix #8)
8.3. B. Truck::describe() on line (X) is not declared const, but the base's describe() const is. Different signatures means it does not override; it is a new function that hides the base's describe for Truck callers, but does not participate in virtual dispatch through Vehicle. When v->describe() is called on a unique_ptr, the virtual dispatch goes to Vehicle::describe(), which returns "vehicle". D is wrong: override is recommended but not required by the language. A would be correct if const were on line (X) (this is exactly why override is helpful: with override the compiler would have errored out and caught this bug). (Study Guide §5)
8.4. A, B, D, F. Version A is safe because the caller's name outlives the call. Version B is a dangling-reference bug (returning a reference to a local); statement B says exactly that, so it is true. Version C is essentially free at runtime due to NRVO (or implicit move on return when NRVO does not apply), so C is wrong (it is not slow) and D is right. E is wrong: the compiler typically warns with -Wreturn-local-addr but the behavior is undefined, not "always rejected" and not "reliably crashes" (it might appear to work in a debug build and crash in a release build, or vice versa). F describes the chaining pattern: std::cout << greet(s); returns the modified name for use in the same expression. (Study Guide §2; Pointers Guide §5)
8.5. B. Line (X) declares bool canLoan() without const. The base's canLoan() is const. Different signatures means line (X) does not override; it is a new (non-virtual, non-overriding) function in Consumable. When p->canLoan() is called through unique_ptr, virtual dispatch finds the base's canLoan() const, which returns true. The code compiles because override is not used; if it were, the compiler would have rejected line (X) and caught the bug. This is the same lesson as 8.3 but with const instead of return type. C is wrong: override is not mandatory. D is wrong: static dispatch is not how virtual functions work; even when calling through a base pointer, virtual functions use dynamic dispatch (but the override has to actually exist). (Study Guide §5)
8.6. B, D. This is the opposite of 8.2 and tests whether you can distinguish "moving a unique_ptr out of a vector" from "destroying the unique_ptr". On line (Y), std::move(it) casts the unique_ptr to an rvalue, and push_back move-constructs discharged_.back() from it. After the move, it is an empty unique_ptr; the Patient itself is still alive on the heap, now owned by discharged_.back(). Line (Z) erases the empty unique_ptr from active_; destroying an empty unique_ptr is a no-op (it has nothing to delete). So alice still points at a valid Patient: line (W) is safe. A and E are wrong (no use-after-free; the Patient is alive in discharged_). C is wrong (erase destroys the unique_ptr slot, but that slot is empty after the move). F is wrong because this code correctly avoided the 8.2 bug by moving ownership before erasing; same "bug class" only if you misread line (Y). (Study Guide §6; Pointers Guide §9, §10)
---
If you finished and scored well on Section 8, you understand the material. If you missed any, the combination of Study Guide §5/§6 and Pointers Guide §9-§10 + Appendix #8 is the right place to review.