Bank 3 — Constructors and lifecycle¶
Twelve exercises on when objects are born, in what order, and when they die. Module 3. Levels L1–L6.
Solutions are in Solutions and answers; nudges are in Hints. Attempt first.
Exercises¶
E24 — Three jobs L1 concept¶
Task. In one sentence each, state the job of: (a) a constructor, (b) a destructor, (c) a default constructor. Then answer: how many times does a destructor run for an object that was never successfully constructed?
E25 — Construction and destruction order L2 predict¶
#include <iostream>
class Engine {
public:
Engine() { std::cout << "engine on\n"; }
~Engine() { std::cout << "engine off\n"; }
};
class Car {
public:
Car() { std::cout << "car on\n"; }
~Car() { std::cout << "car off\n"; }
private:
Engine engine_;
};
int main() {
Car car;
}
Task. Write the exact output, in order. State the rule that decides whether "car on" or "engine on" comes first, and whether destruction is the mirror image.
E26 — Sensor register L2 trace scenario¶
A monitoring tool creates two sensors inside a block and one outside it.
#include <iostream>
#include <string>
class Sensor {
public:
explicit Sensor(std::string id) : id_(id) {
std::cout << "register " << id_ << '\n';
}
~Sensor() {
std::cout << "remove " << id_ << '\n';
}
private:
std::string id_;
};
int main() {
Sensor a{"T1"};
{
Sensor b{"T2"};
Sensor c{"T3"};
}
Sensor d{"T4"};
}
Task. Give the six output lines in order. Then say how many Sensor objects are
alive at the moment d is constructed.
E27 — Two ways to build one L2 complete¶
#include <iostream>
#include <string>
class Ticket {
public:
// TODO: a constructor taking (std::string event, int seat)
// TODO: a delegating constructor taking (std::string event) that calls the
// first with seat 0
std::string event() const { return event_; }
int seat() const { return seat_; }
private:
std::string event_;
int seat_;
};
int main() {
Ticket a{"Hamlet", 12};
Ticket b{"Hamlet"};
std::cout << a.event() << ' ' << a.seat() << '\n';
std::cout << b.event() << ' ' << b.seat() << '\n';
}
Task. Write the two constructors so the program prints Hamlet 12 then
Hamlet 0. Which one is delegating, and what does that buy you here?
E28 — The member nobody set L3 debug¶
#include <iostream>
#include <string>
class Reading {
public:
Reading(int value) : unit_("celsius") { value_ = value; }
int value() const { return value_; }
private:
int value_;
std::string unit_;
};
int main() {
Reading r{20};
std::cout << r.value() << '\n';
}
Task. This compiles, but one member is left uninitialised for part of the object's life. Identify it, explain the defect in one sentence, and rewrite the constructor in the form that removes the defect entirely. (The displayed number is not the problem.)
E29 — Acquire in the constructor L3 modify scenario¶
A temperature logger uses a fixed-size buffer that it must allocate when it starts and release when it is destroyed.
#include <iostream>
class Logger {
public:
Logger(int capacity) : capacity_(capacity) {
data_ = new double[capacity_];
}
void log(int index, double value) {
if (index >= 0 && index < capacity_) data_[index] = value;
}
private:
double* data_;
int capacity_;
};
int main() {
Logger log{4};
log.log(0, 21.5);
std::cout << "logged\n";
}
Task. The class allocates but never releases. Add the member that fixes this, and
say in one sentence why the destructor is the right place rather than an explicit
close() the caller must remember.
E30 — Block scope L2 predict¶
#include <iostream>
struct Noisy {
int id;
explicit Noisy(int i) : id(i) { std::cout << "make " << id << '\n'; }
~Noisy() { std::cout << "drop " << id << '\n'; }
};
int main() {
Noisy a{1};
{
Noisy b{2};
Noisy c{3};
}
Noisy d{4};
}
Task. Write all eight output lines in order. Why is c dropped before b?
E31 — List or body? L4 analyze¶
class Part {
public:
Part(int id, std::string name) {
id_ = id;
name_ = name;
}
private:
int id_;
std::string name_;
};
class Better {
public:
Better(int id, std::string name) : id_(id), name_(std::move(name)) {}
private:
int id_;
std::string name_;
};
Task. Both compile and behave correctly. Name the two concrete differences in what
the machine does when each constructor runs, and say which class you would keep and why.
Is Better always better? Give one case where it would not matter.
E32 — Owned twice L4 debug scenario¶
A device manager hands out a buffer. When a device is destroyed it releases the buffer — twice, from two different places.
#include <iostream>
class Device {
public:
Device(int n) : n_(n), data_(new int[n]) {}
~Device() {
delete[] data_;
std::cout << "closed\n";
}
void close() {
delete[] data_;
}
private:
int n_;
int* data_;
};
int main() {
Device d{8};
d.close();
std::cout << "done\n";
}
Task. Describe what actually goes wrong here and when. Then repair the class so that the buffer is released exactly once, whichever route the caller takes, and say which C++ feature you deliberately did not reach for yet (you are only up to Module 3).
E33 — Born on first use L2 predict¶
#include <iostream>
struct Setting {
const char* name;
explicit Setting(const char* n) : name(n) {
std::cout << "init " << name << '\n';
}
~Setting() {
std::cout << "destroy " << name << '\n';
}
};
void configure() {
static Setting s{"theme"};
std::cout << "using " << s.name << '\n';
}
int main() {
std::cout << "before\n";
configure();
std::cout << "after\n";
configure();
}
Task. Write the output in order. How many times was s constructed across the two
calls, and what decides when it is destroyed?
E34 — Design a lifetime guard L5 design scenario¶
A batch job creates a temporary working directory at the start and must remove it,
with all its files, no matter how it ends — normally, by early return, or by a
thrown exception.
Task. Design a class that ties the directory's existence to an object's lifetime. Give:
- the class name and its constructor and destructor, with a one-line responsibility;
- what the class must not allow (copy it? move it? nothing);
- how a caller uses it, in three lines of pseudo-code;
- one sentence on why this is more reliable than calling
cleanup()by hand.
How you will be judged. On whether your design makes "directory removed exactly once, on every exit path" true by construction rather than by discipline.
E35 — Replace the flags L6 refactor scenario¶
A file-processing function opens a handle, then has four exit paths (success, parse error, empty input, size limit), each of which must remember to close the handle. The team keeps finding leaks on the rarer paths.
Task. Write a refactoring plan that replaces the manual open/close discipline with an object whose lifetime is the handle's lifetime. Your plan must:
- show the four exit paths collapsing to one cleanup point;
- state what the object's constructor and destructor do;
- say what happens when an exception is thrown between open and the normal end;
- name one behaviour you must be careful not to change, and how you would prove you did not.
Success. A reader can implement your plan and verify it, without inventing the missing decisions.
Previous topic: Classes and encapsulation · Next topic: Operators and static members · Hints: hints.md · Solutions: solutions.md