8. Rule 0/3/5
In HFT, objects move through lock-free queues and vector buffers under tight latency budgets. When these containers grow, types must support safe, cheap moves without hidden deep copies or double frees. Resource management design directly impacts tail latency and determinism.
struct Buf {
std::size_t n;
int* p;
Buf(std::size_t n): n(n), p(new int[n]{}) {}
~Buf(){ delete[] p; }
};
Part 1.
When vector<Buf> grows, what failure or cost can occur with this definition? Which special members must be provided or prohibited to satisfy the Rule of 0/3/5 and ensure fast, safe growth?
Part 2.
(1) When is Rule of 0 preferable to Rule of 5?
(2) Why mark move constructor and move assignment noexcept for vector<Buf> growth?
(3) Prefer deep-copy or deleted copy here, and when?
(4) How would using std::unique_ptr<int[]> change special members?
(5) How do triviality and standard layout affect ABI and relocation cost?
Answer
Answer (Part 1)
Implicit copy operations will shallow-copy p, causing double delete on destruction; reallocation may perform expensive or unsafe copies. Follow Rule of 5 for an owning raw pointer: delete copy, define noexcept move that transfers and nulls the source, or refactor to Rule of 0 via RAII members.
Buf(const Buf&) = delete;
Buf& operator=(const Buf&) = delete;
Buf(Buf&& o) noexcept : n(o.n), p(o.p) { o.n=0; o.p=nullptr; }
Buf& operator=(Buf&& o) noexcept { if(this!=&o){ delete[] p; n=o.n; p=o.p; o.n=0; o.p=nullptr; } return *this; }
Answer (Part 2)
(1) Rule of 0 is preferred when ownership is expressed via RAII members so no custom special members are needed. It reduces bugs and lets the compiler optimize construction, destruction, and moves.
(2) Standard containers move elements during growth only if moves are noexcept; otherwise they fall back to copies. Marking moves noexcept enables cheap pointer transfers instead of costly deep copies or reallocation failures.
(3) Delete copy for an owning raw pointer to prevent accidental O(n) copies and double frees. Provide deep-copy only when semantics require duplication and the cost is acceptable and bounded.
(4) std::unique_ptr<int[]> makes copy implicitly deleted and move implicitly noexcept. You can rely on defaulted special members (Rule of 0), eliminating manual move/delete logic.
(5) Trivially copyable types can be relocated via memcpy, often passed in registers per ABI, reducing overhead. Non-trivial types require invoking constructors/destructors, increasing relocation cost; standard-layout aids interop, not relocation speed.