CT 301 Homework 1
1. Overview
You will implement an object-oriented inventory system for managing hardware assets, like what a research lab or IT department uses to track equipment. Items can be loaned out (and, you hope, returned), sold, or surplused (retired). The program reads an initial inventory and a list of transactions from files, applies them, writes the final inventory state to a file, and prints reports to the console.
The system is built around one idea: operations select a collection of items, and you then print or act on that collection. Selecting all Razer devices, all equipment older than a date, or the ten most valuable items each produces a collection; printing it, surplusing it, or loaning it are things you do to that collection afterward.
This assignment is about object-oriented design, polymorphism, and memory ownership with smart pointers. You will own items with std::unique_ptr, transfer ownership with std::move, overload operators, and implement selection and aggregation algorithms.
You may not use raw owning pointers, new, or delete anywhere in this assignment. Raw pointers and manual memory management are the subject of Homework 2. You may use the C++ standard library freely.
2. What We Provide, What You Implement
You are given a set of header and source files. Do not modify the provided files.
Provided (plumbing, so you can focus on the design work):
- The interfaces:
item.h,inventory.h,command.h,state.h,loan_record.h. - The file reader, tokenizer, and the two factories (
make_item,make_command) that turn input lines into objects, with full error handling. Date::parse, the driver (main.cpp), and themakefile.- The
std::formatterspecializations, so your types also work withstd::print. - The three private
Inventoryhelpers (extract,gather,owningCollection) and one worked example,surplus, which shows the action pattern. Incommand.cpp,PrintCommand::executeis the worked example. Study these; the rest follow the same patterns.
You implement:
Date: the comparison operators andoperator<<.- The three item classes:
Equipment,Consumable,Borrowed. - The remaining
Inventoryoperations (everything except the one worked example,surplus, and the three provided helpers). - The
Commandexecute()bodies (each mostly calls the matchingInventoryoperation).
3. Learning Objectives
- Polymorphic hierarchy. Implement an abstract
Itembase and three derived types whose behavior differs. Example:Consumable::canLoan()returns false whileEquipment::canLoan()returns true, so loaning a cable is rejected automatically. - Ownership with
unique_ptrandstd::move. Example: loaning an item moves itsunique_ptrfrom the in-stock collection to the on-loan collection; returning it moves it back. You never copy an item. - Owner vs borrower. One part of the program owns each item (is responsible for deleting it); other parts just borrow a pointer to look at it. Example:
select_by("brand","Razer")returns astd::vector<Item*>of plain pointers that let you read items theInventorystill owns. - Operator overloading. Example: you write
Date::operator<soOLDER_THANcan compare dates, andoperator<<so an item prints its own serial and fields (C++ has no universaltoString; a type renders itself through the stream operator). - Algorithms over a collection. Example:
select_top_by_valuesorts in-stock items by value;value_by_categoryfolds items into a per-category sum. - RAII and invariants. Example: because items are owned by
unique_ptr, they free themselves; you write nodelete. An item that is OnLoan cannot be sold.
4. Classes
| Class | Role |
|---|---|
Item (abstract) | Polymorphic base for all items. |
Equipment, Consumable, Borrowed | Concrete item types deriving from Item. |
Date | Value type with overloaded comparison and << operators. |
LoanRecord | Ties a borrowed item to a borrower and date. |
Command (abstract) + derived | One per transaction verb; runs via execute(RunContext&). |
Inventory | Owns all items; selection, action, and aggregation operations. |
enum class State | InStock, OnLoan, Sold, Surplus. |
4.1 Item hierarchy and identity
The abstract Item base has a virtual destructor and pure virtual functions: canLoan(), currentValue(), a generic attribute(key) used for selection, and a render hook printTo() used by operator<<.
Each item's unique ID is its serial number where one exists; items with no serial (a cable) get a minted tag such as CXY-01. Items of the same kind are grouped by shared attributes such as brand and model: ten identical laptops are ten items, ten serial IDs, same brand and model.
- Equipment: durable hardware the lab owns. Loanable, sellable, surplusable. Depreciates over time (Section 7) and carries condition flags (below). Attributes:
brand,model,category,cpu,date, plus the condition attributes. - Consumable: used up; tracked as a bulk lot with a
quantity. Not loanable; can be sold or surplused. Attributes:category,quantity(andbrand/modelif present). - Borrowed: hardware loaned to the lab by someone else. Because it is not ours, it cannot be loaned out, sold, or surplused; it can only be returned to its lender. Its value to the lab is 0. Attributes:
lender,returnby,brand,model,category.
Equipment condition. A piece of equipment can independently be obsolete and/or broken, and has two vendor dates. These combine (an item can be both obsolete and broken at once), so they are flags, not a single state:
- obsolete (true/false): end-of-life or unsupported, but still usable. Informational only: it does not change value or loanability (a Magic Leap 1 still runs).
- broken (true/false): does not function. Its
currentValue()becomes0.00and it cannot be loaned. - warranty_end and support_end (dates, or
-): when the vendor warranty and vendor support end. Past the warranty date there is no return path, which the system reports as NoRMA (no return-merchandise authorization), a computed condition. - surplus candidate (computed): an item is flagged a surplus candidate when it is broken AND NoRMA (broken with no warranty left to return it under). These are the items worth retiring.
Each derived type answers attribute() for its own fields and returns nothing for a field it does not have.
Behavior differs by type:
bool Consumable::canLoan() const { return false; } // a cable cannot be loaned
bool Equipment::canLoan() const { return true; } // a laptop can
// attribute() answers the keys this type has, else defers to the base:
std::optional<std::string> Equipment::attribute(std::string_view key) const {
if (key == "brand") return brand_;
if (key == "cpu") return cpu_;
if (key == "broken") return broken_ ? "true" : "false";
if (key == "obsolete") return obsolete_ ? "true" : "false";
if (key == "norma") return noRMA() ? "true" : "false"; // computed from warranty_end
if (key == "surplus_candidate")return surplusCandidate() ? "true" : "false";
return baseAttribute(key); // id, name, category, state
}
4.2 Date
A value type (year, month, day). Date::parse (provided) reads YYYY-MM-DD. You write the comparison operators (==, !=, <, <=, >, >=) and operator<<.
Example:
Date a = Date::parse("2021-01-01");
Date b = Date::parse("2024-09-01");
if (a < b) { /* true: your operator< compares year, then month, then day */ }
std::cout << a; // your operator<< prints: 2021-01-01
4.3 LoanRecord
A small record describing one active loan. The inventory creates a LoanRecord when an item is loaned, removes it when the item is returned, and reads it in report() to show who has what.
struct LoanRecord {
std::string itemId; // which item is out (its serial / id)
std::string borrower; // who has it
Date when; // when it was loaned
};
Lifecycle: loan(...) appends a record; return_items(...) erases the matching record; report() prints one line per active record, e.g. SN1207 -> student_ramirez (2026-06-01).
4.4 Command hierarchy
Every transaction line is turned into a small Command object that knows how to run itself. This is how the program executes a file of transactions: the provided factory reads each line and builds the matching command, then the driver calls execute() on each one in order.
All commands run against a shared RunContext (provided), which carries the three things a command might touch:
struct RunContext {
Inventory& inv; // the inventory being modified
std::vector<Item*> selection; // the current selection (carried line to line)
std::ostream& out; // where PRINT / REPORT write
};
A concrete RunContext midway through a run (after BY brand Razer selected two items):
// inv -> the one Inventory holding all items
// selection -> { pointer to SN-KB1 (a Razer keyboard),
// pointer to SN-MS1 (a Razer mouse) } // 2 borrowed pointers
// out -> std::cout
//
// A following PRINT prints those two items; a following SURPLUS
// surpluses them and replaces selection with whatever it rejected.
The command classes are declared for you; you write only their execute() bodies, and each is tiny because it just calls the matching Inventory operation. The whole chain for one line looks like this:
// 1. The transaction line: SURPLUS
// 2. The factory builds: std::make_unique<SurplusCommand>()
// 3. The driver runs: cmd->execute(ctx);
// 4. The execute() body you write (one line):
void SurplusCommand::execute(RunContext& ctx) {
ctx.selection = ctx.inv.surplus(ctx.selection); // act, keep the rejects as the selection
}
// A selection command is just as small:
void SelectByCommand::execute(RunContext& ctx) {
ctx.selection = ctx.inv.select_by(key_, value_);
}
A transaction file, then, is a sequence of these commands sharing one RunContext: a selection command sets ctx.selection, then a later PrintCommand or SurplusCommand uses it.
4.5 Rendering with operator<<
Items and dates render through operator<<. For Date you write the operator directly. For items, a single operator<<(std::ostream&, const Item&) is provided; it calls the virtual printTo(), which you implement in each derived type so it prints that type's serial and fields. Figuring out what each line must contain to match the expected output is part of the assignment (the provided sample shows the target). You do not write any std::formatter; we provide it.
The provided operator<< calls your printTo:
// provided in item.h:
std::ostream& operator<<(std::ostream& os, const Item& it) { it.printTo(os); return os; }
// you write printTo in each derived type so it prints that type's fields:
void Equipment::printTo(std::ostream& os) const {
os << id_ << " EQUIPMENT " << name_ << " ... value=" << currentValue();
}
// then this just works, dispatching to the right printTo:
Item* p = /* an Equipment */; std::cout << *p;
5. Ownership: Who Owns, Who Borrows
- The
Inventoryowns every item asstd::unique_ptr<Item>, partitioned by state into four collections. - When an item changes state, you move its owning
unique_ptrfrom one collection to another; you never copy the object. - Selection returns borrowed pointers, a
std::vector<Item*>pointing at items the inventory still owns. A plain pointer like this is non-owning: it lets you look at the item but it will never delete it (the inventory'sunique_ptrdoes that). This is the one allowed use of a raw pointer. - No raw owning pointers, no
new, nodelete. Storing items by value would slice them.
A state change is a move, not a copy:
// loaning: move the owning unique_ptr from the in-stock vector into the on-loan vector std::unique_ptr<Item> owned = extract(inStock_, id); // remove + take ownership owned->setState(State::OnLoan); onLoan_.push_back(std::move(owned)); // ownership transferred; no copy // (unique_ptr cannot be copied, so the compiler forces you to move)
6. The Inventory Interface
These operations are declared in the provided inventory.h; you implement them (surplus is done for you). A selection returns a collection of borrowed (non-owning) pointers; output and action operations consume one. Each group is shown below with a short example.
6.1 Selection (returns borrowed, non-owning pointers)
Each selection walks the inventory and returns a std::vector<Item*> pointing at items the inventory still owns. group_by instead returns a map from an attribute value to its members. select_by is provided as a worked example.
std::vector<Item*> select_all(); std::vector<Item*> select_by(key, value); // [WORKED EXAMPLE] std::vector<Item*> select_in_state(State s); std::vector<Item*> select_older_than(const Date& d); std::vector<Item*> select_top_by_value(std::size_t n); std::map<std::string, std::vector<Item*>> group_by(key);
Example:
std::vector<Item*> razer = inv.select_by("brand", "Razer"); // all Razer items
std::vector<Item*> top3 = inv.select_top_by_value(3); // 3 most valuable in stock
// group_by returns buckets keyed by the attribute value:
auto byBrand = inv.group_by("brand"); // byBrand["Razer"] is a vector of Item*
6.2 Aggregation
These fold over the items to produce numbers. total_value sums the current value of any collection (provided as a worked example); the others summarize the whole inventory.
double total_value(const std::vector<Item*>& items) const; // [WORKED EXAMPLE] std::map<std::string, double> value_by_category() const; std::map<State, std::size_t> count_by_state() const;
Example:
double worth = inv.total_value(inv.select_in_state(State::InStock)); // value of all in-stock auto perCat = inv.value_by_category(); // perCat["peripherals"] == summed value auto counts = inv.count_by_state(); // counts[State::OnLoan] == number on loan
6.3 Action (returns the subset it could NOT act on)
An action changes the state of each item in the collection and returns the rejects, the items it could not act on (for example, an item that was not in the required starting state). add takes ownership of a new item. surplus is provided as a worked example.
void add(std::unique_ptr<Item> item); // take ownership (move) std::vector<Item*> surplus(const std::vector<Item*>& items); // [WORKED EXAMPLE] std::vector<Item*> sell(const std::vector<Item*>& items); std::vector<Item*> loan(const std::vector<Item*>& items, borrower, when); std::vector<Item*> return_items(const std::vector<Item*>& items);
Example:
auto rejects = inv.surplus(inv.select_older_than(Date::parse("2021-01-01")));
// every old in-stock item is now Surplus; rejects holds any that could not be
// surplused (e.g. one that was OnLoan), so you can print or handle them.
inv.add(std::make_unique<Equipment>(/* ... */)); // ownership moves into the inventory
6.4 Output
print is overloaded once per result shape, so you print whatever a selection or aggregation returned. print for a collection is provided as a worked example. report is the whole-inventory summary; write dumps the final state.
void print(const std::vector<Item*>& items, std::ostream& os) const; // [WORKED EXAMPLE] void print(const std::map<std::string, std::vector<Item*>>& groups, std::ostream&) const; void print(const std::map<std::string, double>& table, std::ostream& os) const; void report(std::ostream& os) const; void write(std::ostream& os) const; // final state of all items, sorted by id
Example:
inv.print(inv.select_by("brand", "Razer"), std::cout); // list the Razer items
inv.print(inv.group_by("category"), std::cout); // list each category group
inv.report(std::cout); // the summary (see below)
What report() prints: counts by state (InStock, OnLoan, Sold, Surplus); total current value of in-stock items; total revenue from sold items; each item on loan with its borrower and date; and the surplus count.
Invariants you must enforce: an item is always in exactly one state; an item that is OnLoan cannot be sold or surplused; a Consumable can never be loaned; IDs are unique. (These hold on their own if each action accepts only items in the right starting state.)
7. Value Rules
Grading compares your output exactly, so currentValue() must follow these rules. The reference "today" is 2026-06-01.
- Equipment: if
broken, the value is0.00. Otherwise straight-line depreciation of 20% of the base value per full year since the purchase date, floored at 10% of base. (Example: base 180.00 purchased 3 full years ago is 180 × (1 − 0.60) = 72.00; a very old but working item bottoms out at 18.00.) Theobsoleteflag and the warranty/support dates do not change value. - Consumable: unit value × quantity. (Example: 8.50 × 50 = 425.00.)
- Borrowed: value to the lab is
0.00(the lab does not own it).
Computed conditions (Equipment):
- NoRMA is true when the reference date is past
warranty_end(no warranty left to return under). Ifwarranty_endis-, NoRMA is false. - surplus candidate is true when the item is
brokenAND NoRMA.
Examples:
// working, base 180.00, purchased 3 full years ago: // 180.00 * (1 - 0.20*3) = 72.00 (floor would be 180.00 * 0.10 = 18.00) // broken: value = 0.00 and canLoan() == false, regardless of age // obsolete but working: value unchanged, still loanable (a Magic Leap 1) // broken AND past warranty_end: NoRMA == true -> surplus candidate
8. Transaction Grammar (fixed)
The transactions file keeps a current selection. Selection verbs set it; output and action verbs use it. After an action, the current selection becomes the action's rejects, so a following PRINT lists what could not be done. Invalid lines are reported and skipped.
ADD <TYPE> <ID> <NAME> <CATEGORY> <VALUE> [type-specific fields...] # selection (sets the current selection) ALL BY <KEY> <VALUE> IN_STATE <STATE> OLDER_THAN <DATE> TOP_BY_VALUE <N> GROUP_BY <KEY> # prints the grouping # output (uses the current selection) PRINT REPORT # action (uses the current selection; leaves rejects as the selection) SURPLUS SELL LOAN <BORROWER> <DATE> RETURN
Example (surplus everything bought before 2021, then list what is still on loan):
OLDER_THAN 2021-01-01 SURPLUS IN_STATE OnLoan PRINT
9. Running the Program (Input / Output Contract)
Getting started. Build with the provided makefile, then run the program by giving it three file names on the command line:
make ./inventory data/appx_inventory.txt data/appx_transactions.txt out.txt
The three arguments, in this exact order, are:
- inventory file — the starting items, one per line (read first; each becomes an item).
- transactions file — the commands to apply, one per line, in order.
- output file — the file your program writes the final inventory to. It is created or overwritten; do not create it yourself.
Two different things come out of the program:
- Whatever
PRINT,GROUP_BY, andREPORTproduce is written to the screen (standard output) as each runs. - The final state of every item is written to the output file you named (this is what
writedoes, at the very end).
Checking your work against the sample. A small example is provided with its expected output. Run it and compare:
./inventory data/appx_inventory.txt data/appx_transactions.txt myout.txt > mystdout.txt diff myout.txt data/appx_output.txt diff mystdout.txt data/appx_stdout.txt
When both diffs print nothing, your output matches the sample. Matching the sample is necessary but not sufficient: test the rest of your code yourself (a starter test file, student_tests.cpp, is included).
Running your own tests. A starter harness, student_tests.cpp, is included. Build and run it like this (from the project folder):
g++ -std=c++26 -Wall -Wextra -Wpedantic -g student_tests.cpp date.cpp items.cpp \
inventory.cpp command.cpp tokenizer.cpp file_reader.cpp factories.cpp -o student_tests
./student_tests
Add your own tests as you go. Your grade depends on correct behavior across cases you will not have seen, so test thoroughly.
Command line (fixed):
./inventory <inventory_file> <transactions_file> <output_file>
Inventory input, one item per line. The ID is the serial (or a minted tag). obsolete and broken are true or false; dates are YYYY-MM-DD. Use - for an absent optional field (for example a missing cpu or warranty date). Names use underscores for spaces.
EQUIPMENT <serial> <name> <category> <value> <date> <brand> <model> <cpu> <obsolete> <broken> <warranty_end> <support_end> CONSUMABLE <id> <name> <category> <value> <quantity> <brand> <model> BORROWED <id> <name> <category> <value> <lender> <returnby> <brand> <model>
EQUIPMENT SN8842 BlackWidow_Keyboard peripherals 180.00 2023-02-10 Razer BlackWidow_V4 - false false 2026-02-10 2028-02-10 CONSUMABLE CXY-01 USB-C_Cable cables 8.50 50 - - BORROWED BR-7001 LoanerScope test-bench 0.00 PartnerLab 2026-09-01 Tektronix MDO3
- Output file: every item, sorted by ID, one per line, including its state.
- Console: the output of each
PRINT,GROUP_BY, andREPORT, at the point it runs.
A fully worked sample (sample input files and their exact expected output) is provided so you can match the line and report formatting precisely.
10. Constraints, Tooling, and Build
- Language: C++26, built with GCC 15.2. Use
#include, notimport std;. - Build with the provided makefile, which compiles with
-std=c++26 -Wall -Wextra -Wpedantic -fsanitize=address,undefined -g. Your code must compile with no warnings. - No raw owning pointers, no
new,delete,malloc, orfree. (Non-owningItem*pointers that only borrow an item are allowed.) - You may use any platform (your machine, a VM, the lab or school servers, WSL) as long as you build with GCC 15 and these flags.
About the sanitizers. -fsanitize=address,undefined turns on two runtime checkers:
- AddressSanitizer catches memory errors: leaks, use-after-free, out-of-bounds access.
- UndefinedBehaviorSanitizer catches undefined behavior: signed overflow, null dereference, bad casts, and similar.
Your program must run with zero sanitizer reports. With correct unique_ptr use, items free themselves (RAII), so you write no delete and these checks pass for free.
11. Grading (100 points, fully automated)
Graded automatically. You may resubmit as many times as you like before the deadline. The autograder reports which checks failed so you can iterate.
| Component | Points |
|---|---|
| Compiles cleanly (provided makefile, no warnings) | 5 |
| Runs clean under ASan/UBSan | 10 |
| Bookkeeping actions correct (add, loan, return, sell, surplus) | 15 |
| Selection, grouping, and aggregation correct | 25 |
| Invariants enforced (illegal actions correctly rejected) | 15 |
| Type-specific behavior and value rules correct | 15 |
| Output and report correct and in the specified format | 15 |
Penalty: raw owning pointers, new, delete, malloc, or free in your source | up to −20 |
| The professor reserves the right to award or deduct points for any aspect of the assignment not explicitly listed above. | — |
Requirements like a virtual destructor, correct unique_ptr use, and moving rather than copying are checked indirectly: get them wrong and the program fails to compile, fails the sanitizers, or produces wrong output. The raw-pointer prohibition is checked by scanning your source.
12. Use of AI
You may use AI tools. The exams are weighted heavily and will test whether you actually understand this material, so using AI to produce code you cannot explain will hurt you on the exams. Use AI the way you would use a knowledgeable tutor or documentation: to ask questions, clarify concepts, and check your reasoning, not to generate a solution you do not understand.
13. What to Submit
Submit through the course autograder:
- A single .zip containing all of your
.cppfiles — for exampledate.cpp,items.cpp,inventory.cpp,command.cpp. - Do not include
main.cpp,student_tests.cpp, the headers, the makefile, or any data/text files. The grader supplies its own copies and will reject a submittedmain.cpp. - A short design document as a separate PDF (uploaded alongside the zip on Canvas, not inside it). It must be your own writing, not generated by AI.
Appendix A. Worked Example
A complete run of the system. Your program, given the two input files below, must produce exactly the console output and output file shown. Use this to calibrate your formatting; the autograder compares output this precisely.
A.1 Command line
./inventory appx_inventory.txt appx_transactions.txt appx_output.txt
A.2 Input: appx_inventory.txt
# type id name category value [type-specific fields...] EQUIPMENT SN8842 BlackWidow_Keyboard peripherals 180.00 2023-02-10 Razer BlackWidow_V4 - false false 2026-02-10 2028-02-10 EQUIPMENT SN1207 DeathAdder_Mouse peripherals 70.00 2024-09-01 Razer DeathAdder_V3 - false false 2026-09-01 2027-09-01 EQUIPMENT SN5500 Oscilloscope test-bench 1200.00 2019-05-20 Keysight DSOX1204G - false true 2022-05-20 2024-05-20 CONSUMABLE CXY-01 USB-C_Cable cables 8.50 50 - - BORROWED BR7001 LoanerScope test-bench 0.00 PartnerLab 2026-09-01 Tektronix MDO3
A.3 Input: appx_transactions.txt
# loan the Razer mouse to a student BY id SN1207 LOAN student_ramirez 2026-06-01 # show the state of the lab REPORT # retire broken, out-of-warranty gear (the oscilloscope qualifies) BY surplus_candidate true SURPLUS # list the Razer gear we still have BY brand Razer PRINT
A.4 Console output (stdout)
The REPORT summary, then the PRINT of the two Razer items still present (sorted by id). Note the depreciated values (180.00 to 72.00, 70.00 to 56.00) and that the broken oscilloscope was selected by BY surplus_candidate true and surplused.
=== Inventory Report === InStock: 4 OnLoan: 1 Sold: 0 Surplus: 0 In-stock value: 497.00 Revenue: 0.00 On loan: SN1207 -> student_ramirez (2026-06-01) Surplus count: 0 SN1207 EQUIPMENT DeathAdder_Mouse peripherals Razer DeathAdder_V3 cpu=- purchased=2024-09-01 value=56.00 obsolete=false broken=false warranty=2026-09-01 support=2027-09-01 OnLoan SN8842 EQUIPMENT BlackWidow_Keyboard peripherals Razer BlackWidow_V4 cpu=- purchased=2023-02-10 value=72.00 obsolete=false broken=false warranty=2026-02-10 support=2028-02-10 InStock
A.5 Output file: appx_output.txt
Every item, sorted by id, with its final state. The broken out-of-warranty oscilloscope (SN5500) is now Surplus; the mouse is OnLoan; the borrowed scope stays InStock (it can only be returned to its lender).
BR7001 BORROWED LoanerScope test-bench Tektronix MDO3 lender=PartnerLab returnby=2026-09-01 value=0.00 InStock CXY-01 CONSUMABLE USB-C_Cable cables qty=50 value=425.00 InStock SN1207 EQUIPMENT DeathAdder_Mouse peripherals Razer DeathAdder_V3 cpu=- purchased=2024-09-01 value=56.00 obsolete=false broken=false warranty=2026-09-01 support=2027-09-01 OnLoan SN5500 EQUIPMENT Oscilloscope test-bench Keysight DSOX1204G cpu=- purchased=2019-05-20 value=0.00 obsolete=false broken=true warranty=2022-05-20 support=2024-05-20 Surplus SN8842 EQUIPMENT BlackWidow_Keyboard peripherals Razer BlackWidow_V4 cpu=- purchased=2023-02-10 value=72.00 obsolete=false broken=false warranty=2026-02-10 support=2028-02-10 InStock