C++ Constructors and Object Initialization: Techniques, Order and a Worked Trace

Learn how C++ chooses default, parameterised, delegating and copy constructors, then trace member states and initialization order through complete code.

KnowledgeGate Team

Exam prep & CS education

Updated 10 Nov 20266 min read
C++ Constructors and Object Initialization: Techniques, Order and a Worked Trace

StudentRecord a;, StudentRecord b{17, "Asha", 84};, and StudentRecord c = b; create objects in three different-looking ways, while a later b.setMarks(91) changes an object that already exists. Mixing initialization with assignment makes constructor-selection and output questions harder than they are; ask whether a new lifetime is starting or an existing state is changing. One trace shows how default, parameterised, delegating, and copy constructors establish a, b, and c. If you need the wider language sequence first, begin with the C++ Tutorial: Complete Learning Path in 12 Weeks.

What a constructor establishes when an object begins its lifetime

A constructor is a special class member that establishes an object's initial state. It has the class name, has no return type, and runs during object creation. It can validate inputs or acquire resources, but the StudentRecord trace follows predictable field values through each initialization path.

The sequence matters: storage is obtained, base classes and data members are initialized, and then the constructor body runs. Later member calls and assignments act on an object whose lifetime has already begun. The ISO C++ FAQ on constructors explains why member-initialiser lists are preferred and why declaration order controls member initialization.

A default constructor is any constructor callable with no arguments, not merely one written with zero parameters. Constructor overloading supplies several parameter lists, and overload resolution selects a viable constructor at each creation expression. For the wider connection among classes, objects, and encapsulation, see Object Oriented Technology Explained.

Main object-initialization forms

Form

Constructor selected

What it means

StudentRecord a;

Default

Default-initializes a

StudentRecord a2{};

Default

Direct-list-initializes a2

StudentRecord b{17, "Asha", 84};

Three-argument

Initializes all three fields

StudentRecord c = b;

Copy

Initializes new object c from b

Despite the =, the last row is initialization because c is being declared. By contrast, b.setMarks(91); is later mutation. A later c = b; would request copy assignment, but this exact class rejects it because const int roll_ makes the implicitly generated copy-assignment operator deleted.

Braces also expose narrowing. StudentRecord bad{17, "Asha", 84.5}; is rejected because double would narrow to int. StudentRecord bad(17, "Asha", 84.5); permits the standard conversion and discards the fractional part. This topic also fits the broader Coding & DSA Courses for Placements.

Worked example: default, parameterised, delegating and copy construction

cpp
#include <iostream>
#include <string>

class StudentRecord {
private:
    const int roll_;
    std::string name_;
    int marks_;

public:
    StudentRecord() : StudentRecord{0, "Unassigned", 40} {
        std::cout << "default\n";
    }

    StudentRecord(int roll, const std::string& name, int marks)
        : roll_{roll}, name_{name}, marks_{marks} {
        std::cout << "parameterized\n";
    }

    StudentRecord(const StudentRecord& other)
        : roll_{other.roll_}, name_{other.name_}, marks_{other.marks_} {
        std::cout << "copy\n";
    }

    void setMarks(int marks) { marks_ = marks; }

    void print() const {
        std::cout << roll_ << ' ' << name_ << ' ' << marks_ << '\n';
    }
};

int main() {
    StudentRecord a;
    StudentRecord b{17, "Asha", 84};
    StudentRecord c = b;

    b.setMarks(91);

    a.print();
    b.print();
    c.print();
}

Creating a first enters the target three-argument constructor with 0, "Unassigned", and 40, so it prints parameterized. Only after that target finishes does the delegating default-constructor body print default. Creating b initializes 17, "Asha", and 84, then prints parameterized. Creating c copies those current values from lvalue b, then prints copy.

Next, b.setMarks(91) changes only b.marks_, from 84 to 91. The separate c.marks_ remains 84. The exact output is:

Code
parameterized
default
parameterized
copy
0 Unassigned 40
17 Asha 91
17 Asha 84
Flow diagram tracing StudentRecord a, b and c through the default, parameterized and copy constructors to their final field values.

Member-initialiser lists initialize; constructor-body statements come later

In the three-argument constructor, : roll_{roll}, name_{name}, marks_{marks} is the member-initialiser list. All three members hold their initialized values before the std::cout statement in the body runs.

Writing roll_ = roll; in the body cannot initialize the const member. If name_ were omitted from the list, it would first be default-constructed, and name_ = name; in the body would be assignment to an existing string.

cpp
class SafePair {
    int base_;
    int doubled_;
public:
    explicit SafePair(int n) : base_{n}, doubled_{base_ * 2} {}
};

For SafePair p{10}, declaration order gives base_ = 10, followed by doubled_ = 10 * 2 = 20. If declarations are reversed but the list stays base_{n}, doubled_{base_ * 2}, doubled_ still initializes first and reads base_ too early. There is no valid portable numeric result. Declare dependent members in dependency order, then list initializers in that same order.

Side-by-side SafePair diagram: safe declaration order yields doubled_ = 20, reversed order reads base_ before it is initialized.

Combine in-class defaults, delegation and explicit construction deliberately

StudentRecord() delegates by naming exactly one target constructor in its initializer. Writing StudentRecord(0, "Unassigned", 40); as a body statement would instead create a separate temporary, not initialize the current object.

cpp
class AttemptPolicy {
    int attempts_ = 3;
    bool negativeMarking_ = false;
public:
    AttemptPolicy() = default;
    explicit AttemptPolicy(int attempts) : attempts_{attempts} {}
};

AttemptPolicy standard{};  // attempts_=3, negativeMarking_=false
AttemptPolicy custom{5};   // attempts_=5, negativeMarking_=false

A constructor initializer overrides the corresponding in-class default; an omitted member keeps its default. Thus custom has attempts_ = 5 and negativeMarking_ = false. AttemptPolicy implicit = 5; is rejected because the one-argument constructor is explicit, while AttemptPolicy direct{5}; is valid. = default requests normal compiler behaviour. = delete can deliberately forbid an operation, such as no-argument construction.

Common constructor traps and exact repairs

  • int StudentRecord(...) -> declares something other than a constructor -> remove the return type.

  • StudentRecord x(); -> declares a function named x -> write StudentRecord x{};.

  • Calling another constructor in the body -> constructs a temporary -> delegate in the initializer list.

  • Assigning const roll_ in the body -> compilation fails -> initialize it in the list.

  • Trusting initializer-list order -> may read a later-declared member too early -> follow declaration order.

  • Treating StudentRecord c = b as assignment -> names the wrong operation -> identify copy construction.

  • Assuming the copy shares marks_ -> predicts c changes with b -> remember that each object owns its own int.

There is another trap: declaring only StudentRecord(int, const std::string&, int) does not also provide a usable no-argument constructor. Add a real default constructor, or justified = default, only when a no-argument state is meaningful. Use braces when you want their narrowing check, and mark a converting one-argument constructor explicit unless implicit conversion is intentional. explicit does not change three-argument construction.

How exam-style and interview questions probe constructor semantics

Constructor questions typically test overload selection, declaration order, and final object state:

  1. First two lines for StudentRecord a? parameterized, then default.

  2. After b.setMarks(91), what print? b: 17 Asha 91; c: 17 Asha 84.

  3. What does StudentRecord x(); declare? A function.

  4. What controls member initialization order? Declaration order in the class.

  5. Is StudentRecord c = b construction or assignment? Copy construction.

List initialization rejects narrowing, and const roll_ prevents implicit copy assignment in this class. StudentRecord bad{17, "Asha", 84.5}; is rejected for list narrowing. A later c = b; is rejected for this class because const roll_ prevents implicit copy assignment. This is not a rule that every class with a copy constructor rejects assignment.

Practise constructor selection, declaration order, narrowing, copy construction, and object state until each trace is mechanical. For wider placement-oriented C++ practice, use Coding For Placements.

C++ constructors: the short version and the next step

  • Constructors establish initial state.

  • The member-initialiser list runs before the body.

  • The creation expression selects default, parameterised, or copy construction.

  • Delegation initializes the current object through one target constructor.

  • Members initialize in declaration order.

The trace is a -> 0, Unassigned, 40; b -> 17, Asha, 84 then 91; c copies b at 84 and remains 84. Now add StudentRecord d{28, "Kabir", 73}; StudentRecord e = d; d.setMarks(79);. Predict before running: d prints 28 Kabir 79; e prints 28 Kabir 73.

Continue with the C++ Programming course for a structured language sequence. Coding for Placements adds wider multi-language practice. Keep initialization and later assignment separate, and constructor traces become mechanical.