Exercises¶
1. Basic Class Implementation with Encapsulation¶
Create a complete BankAccount class demonstrating proper encapsulation and member initialization.
Requirements:
Define a class
BankAccountin thebankingnamespace with private attributes:account_number_(std::string)balance_(double)is_active_(bool, defaulttrue)
Implement:
Default constructor: account number “UNKNOWN”, balance 0.0
Parameterized constructor: takes account number and initial balance
Use member initializer lists for both constructors
Provide accessors (all
const noexceptwith[[nodiscard]]):get_account_number()returnsconst std::string&get_balance()returnsdoubleis_active()returnsbool
Provide mutators:
deposit(double amount)— validateamount > 0, throwstd::invalid_argumentif notwithdraw(double amount)— validate sufficient balance andamount > 0deactivate()— setsis_active_tofalse(noexcept)
Tasks:
Create a project with
include/bank_account.hppandsrc/bank_account.cppWrite
main.cppdemonstrating all operationsAdd Doxygen comments to all public members
Compile with
-Wall -Wextra -pedantic-errors -WerrorVerify: Attempting to access private members directly fails to compile
2. Using std::optional for Safe Search Operations¶
Implement search functions that return std::optional instead of magic values.
Requirements:
Implement
std::optional<size_t> find_first_vowel(std::string_view text)that:Returns the index of the first vowel (a, e, i, o, u, case-insensitive)
Returns
std::nulloptif no vowel foundMark as
[[nodiscard]] noexcept
Implement
std::optional<int> parse_int(std::string_view str)that:Safely converts a string to
intReturns
std::nulloptfor invalid input (non-numeric, overflow)Use
std::stoiin a try-catch block
Implement
std::optional<double> safe_divide(double numerator, double denominator)that:Returns
std::nulloptifdenominator == 0.0Otherwise returns the result
Mark as
[[nodiscard]] noexcept
Tasks:
Create test cases demonstrating all three functions
Show three different ways to check for values (
if (opt),has_value(),!= nullopt)Demonstrate all three access methods (
*opt,value(),value_or())Write a function that chains optionals: parse a string to int, then divide by another parsed int
3. Constructor Best Practices: Avoiding Two-Step Initialization¶
Refactor poorly written classes to use member initializer lists correctly.
Given this inefficient code:
class Engine {
private:
int horsepower_;
std::string fuel_type_;
std::vector<std::string> components_;
public:
Engine() {
horsepower_ = 0;
fuel_type_ = "Unknown";
components_ = {"Pistons", "Crankshaft", "Valves"};
}
Engine(int hp, std::string fuel) {
horsepower_ = hp;
fuel_type_ = fuel;
components_ = {"Pistons", "Crankshaft", "Valves"};
}
};
Tasks:
Rewrite both constructors using member initializer lists
Add a third constructor taking
hp,fuel, andcomponentsvectorMeasure performance difference (create 100,000 objects in a loop, time both versions)
Explain why the original version is inefficient (hint: count constructor calls for
std::vector)Add
const std::string serial_number_member and show why it requires initializer listBonus: Add a reference member
const Engine& base_model_and initialize it properly
4. std::string_view vs. const std::string& Performance¶
Compare the performance and flexibility of different string parameter types.
Requirements:
Implement three versions of
count_spaces:// Version 1 size_t count_spaces_ref(const std::string& text); // Version 2 size_t count_spaces_view(std::string_view text); // Version 3 (substring version) size_t count_spaces_substr(std::string_view text, size_t start, size_t length);
Tasks:
Test all versions with:
std::stringobjectsString literals
const char*pointersSubstrings (using
substr()for Version 1,substr()for Version 2)
Time each version processing a large file (1 MB text, 1000 iterations)
Show that Version 2 accepts all inputs without conversion overhead
Demonstrate that Version 3 can extract substrings without allocation
Anti-pattern: Show why using
string_viewin a constructor that storesstd::stringis wrong:// ❌ Wrong class Document { std::string title_; public: Document(std::string_view title) : title_{title} {} // Still copies! }; // ✓ Correct class Document { std::string title_; public: Document(const std::string& title) : title_{title} {} };
5. Complete Vehicle System Implementation¶
Design and implement a complete class following all lecture best practices.
Requirements:
Implement a Bicycle class in the transportation namespace with:
Attributes (all private):
brand_(std::string)gear_count_(int)current_gear_(int, default 1)is_locked_(bool, defaulttrue)
Constructors:
Default: brand=”Generic”, gear_count=1
Parameterized: takes brand and gear_count, validates
gear_count > 0Use member initializer lists for both
Methods:
shift_up()— increases gear ifcurrent_gear_ < gear_count_and not lockedshift_down()— decreases gear ifcurrent_gear_ > 1and not lockedlock()/unlock()— toggle lock state (noexcept)get_current_gear()— accessor ([[nodiscard]] const noexcept)
Advanced:
get_gear_ratio()— returnsstd::optional<double>calculating ratio based on current gearReturns
std::nulloptif lockedFormula:
2.0 + (current_gear_ - 1) * 0.5
Tasks:
Create complete project structure:
include/,src/,CMakeLists.txtAdd full Doxygen documentation
Write
main.cppwith comprehensive test cases:Create bicycles with stack and heap allocation
Test all methods including error cases
Demonstrate
optionalusage withvalue_or()
Validation tests:
Verify shifting when locked returns
falseVerify cannot shift beyond gear limits
Verify
get_gear_ratio()returnsnulloptwhen locked
Create a simple UML class diagram showing:
Class name (
Bicycle)All attributes with types and visibility
All methods with return types and visibility
Mark
constmethods appropriately
6. Scoped Enums (enum class) for Type-Safe Presets¶
Replace fragile string fields with strongly typed scoped enums and provide conversion utilities.
Requirements¶
In namespace
transportation, define:enum class Color { Red, Blue, Green, Black, White, Silver, Unknown }; enum class Model { Sedan, SUV, Truck, Coupe, Hatchback, Unknown };
Refactor a class
Vehicleto use these enums:class Vehicle { private: Color color_{Color::Unknown}; Model model_{Model::Unknown}; bool is_running_{false}; int max_speed_{0}; public: Vehicle(Color color, Model model, int max_speed); void start_engine(); void stop_engine(); void drive(); [[nodiscard]] Color color() const noexcept; [[nodiscard]] Model model() const noexcept; [[nodiscard]] int max_speed() const noexcept; };
Implement the constructor with a member initializer list and validate
max_speed > 0(throwstd::invalid_argumentotherwise).All accessors must be
[[nodiscard]]andnoexcept.
Provide readable conversions:
std::string to_string(Color c); std::string to_string(Model m);
Implement via
switchwith no default and an explicitreturnfor each enumerator.Optionally add a
default:returning"Unknown"if your compiler warns for missing cases.
Provide parsing helpers that do not throw:
std::optional<Color> parse_color(std::string_view s) noexcept; std::optional<Model> parse_model(std::string_view s) noexcept;
Accept case-insensitive inputs such as
"red","RED", or"Red".Return
std::nulloptfor unknown tokens.
Overload stream insertion for quick debugging:
inline std::ostream& operator<<(std::ostream& os, Color c) { return os << to_string(c); } inline std::ostream& operator<<(std::ostream& os, Model m) { return os << to_string(m); }
Tasks¶
Header/Source split:
Place enum declarations and conversions in
include/transportation/types.hppand their definitions insrc/types.cpp.Implement
Vehicleininclude/vehicle.hppandsrc/vehicle.cpp.
Factory function:
std::optional<Vehicle> make_vehicle(std::string_view color, std::string_view model, int max_speed) noexcept;
Use
parse_colorandparse_model.Return
std::nulloptif parsing fails ormax_speed <= 0.
Compile-time checks:
Add
static_assert(std::is_enum_v<Color> && std::is_enum_v<Model>);Optionally fix the underlying type:
enum class Color : std::uint8_t { ... }; enum class Model : std::uint8_t { ... };
Demonstration program (``main.cpp``):
Construct vehicles using both enums directly and via
make_vehicle("Red", "SUV", 180).Print them using
operator<<.Show behavior for invalid input (e.g.,
"Purple"or"Spaceship") and handlestd::nulloptusingvalue_oror conditional checks.Verify that accidental assignments like
color_ = "Red";no longer compile.
Doxygen:
Document each enumerator and all conversion functions.
Mark non-throwing functions as
noexceptand add\\warningon parsing about accepted spellings and case-insensitivity.
Validation Criteria¶
Assigning a string literal to
color_ormodel_does not compile.to_stringcovers all enumerators; missing cases trigger warnings under-Wall -Wextra -Werror.parse_colorandparse_modelreturnstd::nulloptfor unknown tokens and never throw.make_vehiclereturns a disengagedstd::optionalfor invalid input.All public methods and conversion helpers are documented and pass basic unit tests.
Bonus¶
Serialization:
Implement
to_json/from_jsonadapters fornlohmann::jsonmapping enums to strings via your conversion helpers.Hashing:
Provide a hasher for use in unordered containers:
struct ColorHash { std::size_t operator()(Color c) const noexcept { return static_cast<std::size_t>(c); } };
Underlying type control:
Use
enum class Color : std::uint8_tandenum class Model : std::uint8_tto reduce footprint, and explain trade-offs in your report.