Forecast: guess before reading — does line (1) give you x=0, y=0, or does it fail?
Look for a no-argument constructor.Why this step? Line Point p; needs a constructor callable with zero arguments — that is the definition of a default constructor.
We declared Point(int,int).Why this matters? The moment you declare any constructor, the compiler stops synthesizing the default one. So there is noPoint() available.
Verdict. Line (1) is a compile error; line (2) compiles: x=3, y=4. Why? Because step 2 removed the only ctor that could serve Point p;, while Point q(3,4) exactly matches the Point(int,int) we did declare.
The fix.Why? To keep both behaviours, explicitly restore the default:
Point() = default; // brings back the synthesized default constructorPoint(int a, int b) : x(a), y(b) {}
Forecast: which of (1)(2)(3) survive the explicit keyword?
explicit blocks the implicit conversion path.Why this step?explicit forbids the compiler from silently turning an int into a Meter unless you ask directly.
Line (1)Meter a(5) — direct-initialization, you named the type. Why OK? Direct-init is always allowed. a.m == 5.
Line (2)Meter b = 5 — copy-initialization from an int. Why blocked? This form asks for an implicitint→Meter conversion; explicit rejects it → compile error.
Line (3)Meter c = Meter(5) — you wrote Meter(5)explicitly, then copy that Meter. Why OK? The conversion is now spelled out; the copy of an existing Meter is fine. c.m == 5.
See explicit keyword and implicit conversions for the full rules.
The synthesized copy ctor does member-wise copy.Why this step? For Point the members are two ints — copying an int copies its value, not a shared address.
q gets its own independent x and y.Why safe? No pointer is shared; q.x was set to 3 at the copy, and p.x = 99 touches only p.
Result:q.x == 3, q.y == 4, p.x == 99. Why? Because q copied p's values (3 and 4) into its own storage back when the copy happened; the later p.x = 99 writes only to p's memory, which q does not share.
Below: Figure 1 — left panel shows Version A (shallow copy) where a.data and b.data are two arrows pointing to one shared cyan buffer, so both destructors free the same block. The right panel shows Version B (deep copy) where each arrow points to its own amber buffer.
Forecast: how many times is the same address passed to delete[]?
Version A shallow-copies the pointer.Why dangerous? Look at the left panel of Figure 1: a.data and b.data hold the same address (the cyan buffer). Two owners, one buffer.
Both destructors run.Why fatal? When b dies, delete[] b.data frees the buffer. When a dies, delete[] a.data frees the already-freed buffer → double free, i.e. Undefined Behaviour (UB) — the standard guarantees nothing; typically a crash or heap corruption.
Version B — deep copy (the right panel of Figure 1): allocate a fresh buffer and copy element-by-element.
Forecast: does n = 0 crash, or is it a valid empty object?
new int[0] is legal in C++.Why? It returns a valid, non-null pointer that you may safely pass to delete[]. The standard does not promise the address is distinct from every other allocation — it only guarantees the pointer is well-formed for delete[]. You may not dereference it.
The copy loop for (i=0; i<0; ...) never executes.Why safe? The condition 0 < 0 is false immediately — zero iterations, no out-of-bounds read.
Destruction:delete[] data on a pointer from new int[0] is well-defined. Both a and b free their own (empty) allocations.
Result:a.n == 0, b.n == 0, no read/write of any element, no crash. Why? Because we sized the copy from o.n (which is 0), the allocation is empty and the loop bound is 0, so no element is ever touched — the degenerate input flows through safely instead of hitting a hardcoded size.
Forecast: guess — is this == &o ever possible inside a copy constructor?
A copy constructor builds a brand new object.Why this matters?this points to memory that did not exist a moment ago (b is being constructed). The source o is an already-living object. They are necessarily different objects.
So this == &o is impossible.Why? An object cannot be its own not-yet-constructed self. Contrast with assignment a = a; where this == &otheris possible and must be guarded.
Consequence: the deep-copy loop data[i] = o.data[i] is always reading a different buffer than the one it writes — no aliasing hazard.
Verify by construction: for Buf b(a), addresses &a != &b, and b.data != a.data after deep copy. Why? Because b occupies freshly allocated storage distinct from a, and the deep-copy ctor allocates a new inner buffer, so both the object addresses and the buffer addresses differ.
Below: Figure 2 — left panel is a legal delegation tree: Point() and Point(int) both arrow into the single amber TARGETPoint(int,int). Right panel is an illegal cycle: A() arrows to A(int) which arrows back to A(), a loop that never reaches a target.
Forecast: predict p, q, r's (x,y) before tracing.
Point p; calls Point() → delegates to Point(0,0).Why this step? The delegate's list contains onlyPoint(0,0); the target runs fully first, setting x=0,y=0. Result p = (0,0).
Point q(7); calls Point(int) → delegates to Point(7,7). Result q = (7,7).
Point r(2,3); calls the target directly. Result r = (2,3).
Why can't a delegate also init members?Why the restriction? By the rule above, the target Point(int,int) is the sole place members get set. If a delegate wrote : Point(0,0), x(9), the object would be initialized twice — forbidden, so C++ only allows the single delegating call (Figure 2, left tree).
The cycle (right panel).Why fatal? If A() delegates to A(int) and A(int) delegates back to A(), construction never reaches a real target → Undefined Behaviour (UB) / infinite loop. Delegation must form a tree ending at a genuine target.
Forecast: you might guess a=5, b=5. Look again at the declaration order.
Init order = declaration order, not list order.Why? The compiler always initializes members in the order they appear in the class body, regardless of how you sequence the initializer list.
b is declared first → b is initialized first, using b(a). Why is this a bug? At this instant a is not yet initialized (it's declared afterb). So b is set from an indeterminatea → garbage.
Then a is initialized from a(v) → a = 5. Correct, but too late for b.
Result:a == 5, b == <indeterminate>.
See Member Initializer Lists for the full mechanics.
Forecast: which ctor fires on each line, and what balance results?
Line (1)BankAccount alice; → default → delegates to BankAccount(0.0). Why? No argument means the "born from nothing" recipe, which we routed to the target with 0.0 — one place defines "empty account" (the DRY = Don't-Repeat-Yourself principle in action). alice.bal() == 0.0.
Line (2)BankAccount bob(250.0); → parameterized target directly. bob.bal() == 250.0.
Line (3)BankAccount joint = bob; → copy constructor (synthesized; only a double member, so member-wise copy is safe). joint.bal() == 250.0, independent of bob.
Sanity (units). Balance is money; all three are non-negative doubles, matching the real-world constraint that a fresh or copied account is well-defined.
Line (1) is a default construction, not a copy.Why?Tracer a; invokes Tracer(), so copies stays 0 so far.
Passing a by value into byValue.Why a copy? The parameter t is a new object built from a, which invokes the copy ctor → +1 copy (copies becomes 1).
return t; returns by value.Why another copy? The returned Tracer (used to build b) is a new object built from t, invoking the copy ctor again → +1 copy (copies becomes 2). This is the one copy elision / RVO usually removes.
Total ignoring elision: 1 + 1 = 2 copies.Why "ignoring elision"? Modern compilers may elide the return copy (and since C++17 must elide certain temporaries), reducing observed copies to 1 — but the language model without elision counts 2.
Exam-safe answer: state 2, then note "compilers may elide to 1." See Move Semantics — move constructor for how moves change this count.