5.2.6 · D4C++ Programming

Exercises — Constructors — default, parameterized, copy, delegating

2,286 words10 min readBack to topic

Level 1 — Recognition

Recall Solution L1·Q1
  • (i) Default — no arguments supplied, so Point() runs.
  • (ii) Parameterized — two ints go to Point(int,int).
  • (iii) Copyc is born as a copy of the already-built b. This is copy-initialization, but it still calls the copy constructor Point(const Point&).
  • (iv) Copy — direct form Point d(b); same copy constructor as (iii).

The key tell: what is on the right? Nothing → default. Raw values → parameterized. An existing object → copy.

Recall Solution L1·Q2
  • (a) Invalid — a constructor has no return type, not even void. Writing void turns it into an ordinary member function named Point.
  • (b) Valid — the default constructor.
  • (c) Valid — a parameterized (single-arg) constructor.
  • (d) Invalidint return type; same reason as (a).

Rule to memorise: same name as the class, and nothing where a return type would go.


Level 2 — Application

Recall Solution L2·Q1
Rect(int a, int b) : w(a), h(b), area(a * b) {}

Why the list and not the body? In the list each member is direct-initialized once. If you did area = a*b; inside {}, area would first be default-initialized to garbage, then assigned — two steps, and for const members it would be impossible. Watch the order: area is declared last, so it is initialized last — after w and h exist. Good. Result: w=3, h=4, area=12.

Recall Solution L2·Q2
  • (i) Compiles — direct-initialization is always allowed.
  • (ii) Rejected= 5 needs an implicit int → Meter conversion, which explicit forbids.
  • (iii) CompilesMeter(5) is an explicit conversion (you named the type), then copy-initialized into c.

See explicit keyword and implicit conversions for the full story.


Level 3 — Analysis

Recall Solution L3·Q1

Counting copy-constructor calls with elision disabled:

  • Line 1: default constructor, 0 copies.
  • Line 2: copy-init b from a1 copy.
  • Line 3: passing a by value builds the parameter x from a1 copy.
  • Line 4: make() builds local, returns it by value → 1 copy (return), then c is copy-initialized from that returned temporary → 1 copy. That is 2 copies.

Total = 4 copies. (In real C++17 compilers, mandatory copy-elision would erase the line-4 copies, giving 2 — but the question fixed "no elision" to test understanding of when the copy constructor is eligible to fire.) See Move Semantics — move constructor for how moves replace many of these.

Recall Solution L3·Q2

The compiler-supplied copy constructor does a member-wise (shallow) copy: it copies the pointer value data, so x.data == y.data — both point at the same heap array. Nothing bad happens yet. The disaster is deferred to destruction: when y dies its destructor runs delete[] data; when x dies its destructor runs delete[] on the same already-freed pointer → double free, undefined behaviour / crash. Fix: write a deep-copy constructor (data(new int[o.n]) then copy elements), plus copy assignment and destructor — the Rule of Three Five Zero.


Level 4 — Synthesis

Recall Solution L4·Q1
class Circle {
    int x, y, r;
public:
    Circle(int x, int y, int r) : x(x), y(y), r(r) {}  // target
    Circle(int r)  : Circle(0, 0, r) {}                 // delegates
    Circle()       : Circle(0, 0, 1) {}                 // delegates
};

Why this shape? The target holds the only member-initialisation. Delegates carry just the delegating call in their list — you may not also list members there. The target runs fully first, then the delegate's (empty) body runs. Circle c; ends with x=0, y=0, r=1; Circle d(5); ends with x=0, y=0, r=5. See Member Initializer Lists.

Recall Solution L4·Q2
Buf(const Buf& o) : data(new int[o.n]), n(o.n) {
    for (int i = 0; i < n; ++i) data[i] = o.data[i];
}
  • data(new int[o.n]) — allocate a separate array of the same length.
  • the loop copies each element so contents match. After this, y.data == x.data is false (distinct addresses), while the contents are equal. Now each object owns its own memory, so two delete[]s free two different arrays — no double free.

Level 5 — Mastery

Recall Solution L5·Q1

Constructing box: members build in declaration order (a then b), not list order:

  • a('A') → prints CA
  • b('B') → prints CB So far: CACB.

M solo('S') → parameterized → CS. Now: CACBCS.

M dup = solo → copy constructor → Kdup uses solo.id = 'S' → prints KS. Now: CACBCSKS.

Destruction at end of main — reverse order of construction. Objects were completed in the order box, solo, dup; local destruction is LIFO, so: dup first, then solo, then box. And inside box, members destruct in reverse declaration order (b then a):

  • dupDS
  • soloDS
  • box: bDB, then aDA

Destruction tail: DSDSDBDA.

Full output: CACBCSKSDSDSDBDA

Recall Solution L5·Q2

A() delegates to A(int), which delegates back to A() — a delegation cycle A() → A(int) → A() → …. No constructor ever actually initialises v; this is undefined behaviour / non-terminating construction. Minimal fix: make one constructor a real target that initialises members directly, and let the other delegate to it:

struct A {
    int v;
    A(int x) : v(x) {}   // TARGET — sets v, no delegation
    A()      : A(0) {}   // delegates to the target
};

Now delegation forms a tree ending at a genuine initialiser. A a; gives v=0; A b(7); gives v=7.


Figures

Figure — Constructors — default, parameterized, copy, delegating
Figure — Constructors — default, parameterized, copy, delegating

Recall One-line self-test

Predict the L5·Q1 output without peeking ::: CACBCSKSDSDSDBDA