Quant developer HFT course · Module 2: Safe C++
Drill: lifetime and ownership
Each prompt is a piece of code an interviewer might show you, followed by "what's wrong?". Find the bug, name the rule it breaks, and say how you would fix it and how you would catch it in future.
Prompt 1: The helpful accessor
The interviewer says
const Level& top(const Instrument& i) { auto book = snapshot(i); return book.levels[0]; } What is wrong with this?
Before you answer
Question · choose every correct option
Which statements are correct? Choose all that apply.
Lifetime extension applies to temporaries bound directly to references, never to locals returned by reference. Even immediate use reads destroyed memory.
Answers that lose the point
It returns a reference, which is faster.
Fast and wrong. The interviewer asked what is wrong.It might work.
It might, in a test. Undefined behaviour that passes tests is the problem, not the defence.
A strong answer
It returns a reference into a local. The book is destroyed at the closing brace, so the caller gets a reference to memory that is no longer a Level. It may pass tests because the stack has not been reused yet, then fail in production.
A level is a price and a quantity, so I would return it by value. If the caller needs to refer to the live level, the function should take the book by reference from an owner that outlives the call, not build its own copy. I would expect AddressSanitizer's stack-use-after-return checks, or GCC's dangling-reference warning, to catch this in CI.
Prompt 2: The acknowledgement callback
The interviewer says
A function creates an order as a local, sends it, and registers [&](const Ack& a) { order.status = a.status; } to run when the exchange acknowledges. Problems?
Before you answer
Question · choose every correct option
Which are real problems? Choose all that apply.
Deferred lambdas outlive their scope; [&] hides what they hold; cross-thread callbacks add a race on top.
Answers that lose the point
Change [&] to [=].
Copies the order, so the update goes to the copy and is lost, and in a member function it still captures this.Make order static.
Removes the dangling and creates shared state across all orders.
A strong answer
The lambda is deferred, so it outlives the function and its reference to the local order dangles. I would have the router own the order in a table, capture the order's id by value, and look it up when the acknowledgement arrives, handling the case where it has been removed.
If the callback runs on the gateway's thread, any shared state it touches needs to be atomic or handed back to the owning thread through a queue. And because it uses members, it captures this, so the router must outlive the gateway's callbacks; I would make that explicit in the shutdown order or use a weak_ptr.
Prompt 3: Shared pointers everywhere
The interviewer says
Every order is a std::shared_ptr<Order>, copied to the risk checker, the gateway and the logger. Is that a problem?
Before you answer
Question · choose every correct option
Which statements belong in the answer? Choose all that apply.
Dereferencing a shared_ptr is a plain pointer load; the costs are in copying, allocation and unpredictable destruction.
Answers that lose the point
shared_ptr is slow, use raw pointers.
Raw pointers lose the ownership information without fixing the design.It's fine, shared_ptr is thread-safe.
The count is thread-safe; the object is not, and the counting is what costs.
A strong answer
Ownership here is not really shared: the order manager owns orders and the other components use them briefly. Copying shared_ptrs costs an atomic increment and decrement per hand-off, on counts several threads touch, plus an allocation per order, and whoever drops the last copy runs the destructor at an unpredictable point.
I would keep orders in a pool owned by the manager, pass references for synchronous use, and pass handles with a generation check for anything that refers to the order later. The logger would receive a copy of the few fields it needs in a fixed-size record, not the order.
Prompt 4: The convenient view
The interviewer says
std::string_view venue() const { return std::string(venue_code_) + "-" + region_; } Comment.
Before you answer
Question · choose every correct option
What is true about this function? Choose all that apply.
string_view never copies. The implicit conversion from a temporary string compiles, which is what makes this bug common.
Answers that lose the point
string_view is faster, so this is good.
Fast access to freed memory is not an optimisation.
A strong answer
It returns a view of a temporary. The concatenated string is destroyed when the return statement finishes, so the caller's view points at freed heap memory. Short strings in the small buffer might even appear to work, which hides it.
If the function builds a new string, it has to return std::string. Better still for a hot path, store the combined venue identifier once at construction and return a view of that member, which lives as long as the object.
Prompt 5: Pointers into a vector
The interviewer says
A book stores orders in a std::vector<Order> and keeps an unordered_map<OrderId, Order*>. Cancels sometimes corrupt memory after a busy period. Why?
Before you answer
Question · choose every correct option
Which are correct? Choose all that apply.
The values in the map are pointers into the vector, and the vector is what moves. Rehashing the standard unordered_map does not move its elements.
Answers that lose the point
Use a std::list instead.
Stable addresses, at the cost of an allocation and scattered nodes per order.Call reserve.
Helps until the busy period exceeds the reserve, and does nothing for erases.
A strong answer
The pointers are into the vector's buffer. When a burst of new orders exceeds the capacity, push_back allocates a new buffer and moves the orders, so every pointer in the map refers to freed memory; erasing from the middle moves later orders too.
I would store orders in a preallocated pool that never moves, map exchange ids to handles made of a slot index and a generation, and check the generation on every lookup so a stale reference is detected rather than followed.
After the drill
Every bug here is the same bug: something that refers to an object outliving the object, or the object moving. The questions to ask in any code review are who owns this, how long does it live, and can it move. Module 3 turns to the containers themselves and what they cost.