Practice Lab 5 — Employee record with a lifecycle trace¶
Difficulty: Beginner · Estimated time: 3 hours · Module 3: Constructors, Destructors and the Object Lifecycle
You will be asked to write the output down before you run the program. Being wrong is the point: the gap between what you expected and what happened is the lesson.
At a glance¶
| Difficulty | Beginner |
| Estimated time | 3 hours |
| Module | Module 3 — Constructors, Destructors and the Object Lifecycle |
| Concepts practised | Default and parameterised constructors, constructor overloading, member initialisation lists, destructors, object lifetime, scope, temporary objects |
| Prerequisite | Practice Lab 4 |
| Class design | given — one class, named for you |
Scenario¶
An HR system keeps employee records. The system's authors are debugging a resource problem: an employee record is supposed to write a log line when it is created and another when it is destroyed, and the log has more lines than there are employees.
Nobody believes the count. That is why every constructor and destructor in this lab narrates itself.
Problem statement¶
Implement a class Employee with:
- a name, an employee number, a department, and an annual salary in whole pence;
- a default constructor that produces a record with an obvious placeholder identity;
- a parameterised constructor taking name, number, department and salary;
- a constructor that takes name and number only, delegating to the four-argument one with sensible defaults for the rest;
- a destructor that narrates itself;
- a
describe()operation.
Every constructor and the destructor print one line, in a fixed format:
Then write a main that produces a fixed, deliberate sequence of object lifetimes:
- A named local object.
- An unnamed temporary passed to a function that takes
Employeeby value. - An object inside a nested block, so that it dies before
mainends. - A named object created with
Employee{}— a temporary used to initialise it. - A fixed array of three employees.
Run it, and in lifecycle-trace.md write down the order you expected before running it, then the
actual order, then the differences.
Learning objectives¶
By the end of this lab you will be able to:
- Write default, parameterised and delegating constructors, and say when each is used.
- Use a member initialisation list and explain why it is not the same as assigning in the body.
- Predict the order of construction and destruction for locals, nested scopes, temporaries and arrays.
- Explain why an object passed by value produces two narrations, and what to pass instead when copying is not intended.
- Say what a destructor is for, and name one thing this lab's destructor does not need to do.
Functional requirements¶
- The placeholder default record is clearly invalid: an empty name, number
0, and a department ofunassigned— and it must not pretend to be a valid employee. - The delegating constructor produces a record with the same defaults as the default constructor for the
fields it does not take — prove it by comparing the two
describe()outputs. - The initialisation list is used for every member that can be initialised there.
- A constant salary default for a department, if you choose to add one, is a
static constexprmember and is not per-object. - The narration format is fixed, so two runs are comparable, and every line ends with a newline.
- Destruction narration reports the same identity as construction.
- Every
describe()isconst.
Constraints¶
- Two files:
employee.handmain.cpp(put the definitions inline for now; a.cppsplit is Module 2's exercise, and a separate file here would hide the narration in a different file frommain). - One class, no inheritance, no smart pointers.
-Wall -Wextra -Werror, no warnings.- The salary is integer pence. Formatting with thousands separators is optional; if you do it, do it in
describeonly. - No
std::endl— use'\n', and say in your notes why that choice is about flushing, not style.
Starter task¶
Before coding, write in lifecycle-trace.md:
- The exact expected narration order for the five parts of
main, as line numbers withctor/dtor. - How many
ctorlines you expect in total, and how manydtorlines. - The answer to this: passing an
Employeeby value to a printing function — one narration or two? Write your expectation down.
Then implement, run, and compare. Record every surprise; do not edit the expectation afterwards.
Implementation tasks¶
- Declare the class with its four fields, including a default value for each where the default constructor would otherwise do nothing.
- Write the three constructors, with the two-argument one delegating.
- Write the narration in one helper function so the format cannot drift between constructors.
- Write
describe. - Write
mainwith the five parts, in the given order. - Run it and record the output.
- Repeat part 2 with the parameter taken by
constreference instead of by value, and record how the count changes.
Test cases¶
| # | Action | Expected result |
|---|---|---|
| 1 | Create a default employee | One ctor line with the placeholder identity; describe reports unassigned |
| 2 | Create a four-argument employee | One ctor line with the real values |
| 3 | Create a two-argument employee | One ctor line, and the same defaults as case 1 for the missing fields |
| 4 | Pass an employee by value to a printing function | Two narrations: the copy and the original both narrate |
| 5 | Pass the same employee by const reference |
Zero extra narrations |
| 6 | A nested block with one employee | Its dtor prints before the enclosing scope's |
| 7 | Employee e = Employee{} |
A temporary narrates, then the named object |
| 8 | A fixed array of three | Three ctor lines, then three dtor lines in reverse order |
| 9 | End of main |
Everything constructed is destroyed exactly once |
| 10 | Count the lines | ctor count equals dtor count |
Boundary cases to run deliberately: an empty name and a name with spaces; the array of exactly one element; a scope that throws (you may defer this to Module 13, but note the expectation).
Expected behaviour¶
Shape of the output — the order is what matters, and you must produce it yourself:
[ctor ] #0 (unassigned) 0 pence
[ctor ] #1001 Ayesha Khan (Data Science) 4500000 pence
[ctor ] #1002 Bilal Ahmed (Operations) 0 pence
[ctor-copy] #1001 Ayesha Khan
[dtor ] #1001 Ayesha Khan
...
[dtor ] #1002 Bilal Ahmed
ctor lines: 9
dtor lines: 9
Extension challenge¶
Give Employee a department-default salary, so that the two-argument constructor produces a salary based
on the department.
Then record:
- Where the salary table lives (a
staticmember, a free function, a class of its own) and why. - Which constructor had to change, and how many callers would notice if the table changed.
- The narration lines added, and whether the count of
ctoranddtorlines is still equal.
Debugging challenge¶
The destructor never runs for one of the objects:
Employee* make_employee() {
Employee local("Zainab", 1003, "Finance", 3800000);
return &local; // "the caller will destroy it"
}
Reproduce it and answer:
- Where does the narration for
localappear, and where does the program then read that object? - What is the lifetime of
local, in one sentence? - Which of these is the right fix: return by value, return a
std::unique_ptr, or take an output parameter by reference? Name the trade-off each one makes, and implement the one you choose. - Write down what would happen if you did destroy the returned object.
Reflection questions¶
- Which of your constructors did work that should have been in a member initialisation list, and what did you notice when you moved it?
- Why does the array's destruction order differ from its construction order, and where is that guaranteed?
- Passing by value produced two narrations. What else about a class makes that expensive, beyond the line it prints?
- Your destructor narrates. Name one thing a destructor in a real system does that a narration would never reveal.
- A
static constexprsalary default is shared by every object. What would go wrong if it were a plain member instead? Employee{}in case 7 is a temporary. What is the difference betweenEmployee e = Employee{};andEmployee e{};, and does it show in the narration?
Completion checklist¶
- [ ] It compiles with
-Wall -Wextra -Werrorand no warnings - [ ] Three constructors, one of them delegating, each narrating in the same format
- [ ] The initialisation list is used for every member it can be
- [ ] The destructor narrates the same identity as the constructor
- [ ] Every
describeisconst - [ ] A prediction of the narration order was written down before running
- [ ] The actual output was compared with the prediction, and every difference is explained
- [ ]
ctorlines anddtorlines are equal in number, and the count is recorded - [ ] All ten test cases run, including both the by-value and by-reference variants
- [ ] The debugging challenge was explained before being fixed, and the chosen fix's trade-off is written
Related: Lab index · Module 3 — Constructors and Destructors · Previous: Practice Lab 4 · Next: Practice Lab 6 — Sensor register: constructors and temporaries