Constructors in C++

What is a Constructor?

A constructor is a special member function that is called automatically when an object is created. It has the same name as the class and is used to initialize the object’s data members.

Purpose of Constructors

The main purpose of constructors is to ensure that objects start their life in a valid and consistent state by:

  1. Initializing data members
  2. Allocating resources needed by the object
  3. Setting up the object’s initial state

Rules for Constructors

  1. Constructor name must be the same as the class name
  2. Constructors don’t have a return type (not even void)
  3. Constructors are automatically called when objects are created
  4. A class can have multiple constructors (constructor overloading)
  5. If no constructor is defined, the compiler provides a default one

Default Constructor

A default constructor is one that takes no arguments. There are two kinds:

  1. Compiler-provided default constructor: Created automatically if you don’t define any constructor
  2. User-defined default constructor: Created by the programmer with no parameters

Compiler-provided Default Constructor

class Box {
private:
    double length;  // Will be initialized with garbage value
    double width;   // Will be initialized with garbage value
    
public:
    void setDimensions(double l, double w) {
        length = l;
        width = w;
    }
};

int main() {
    Box b;  // Compiler's default constructor called
    b.setDimensions(5, 3);
    return 0;
}

User-defined Default Constructor

class Box {
private:
    double length;
    double width;
    
public:
    // User-defined default constructor
    Box() {
        length = 1.0;  // Default value
        width = 1.0;   // Default value
        cout << "Default constructor called" << endl;
    }
};

int main() {
    Box b;  // User's default constructor called
    return 0;
}

Parameterized Constructor

A parameterized constructor takes one or more parameters to initialize an object with specific values.

class Student {
private:
    string name;
    int rollNumber;
    
public:
    // Parameterized constructor
    Student(string n, int r) {
        name = n;
        rollNumber = r;
    }
    
    void display() {
        cout << "Name: " << name << ", Roll: " << rollNumber << endl;
    }
};

int main() {
    // Using parameterized constructor
    Student s1("John", 101);
    Student s2("Alice", 102);
    
    s1.display();  // Output: Name: John, Roll: 101
    s2.display();  // Output: Name: Alice, Roll: 102
    
    return 0;
}

Constructor with Default Arguments

You can also create constructors with default argument values:

class Rectangle {
private:
    double length;
    double width;
    
public:
    // Constructor with default arguments
    Rectangle(double l = 1.0, double w = 1.0) {
        length = l;
        width = w;
    }
    
    double area() {
        return length * width;
    }
};

int main() {
    Rectangle r1;             // Uses default values: 1.0, 1.0
    Rectangle r2(5.0);        // Uses: 5.0, 1.0
    Rectangle r3(5.0, 3.0);   // Uses: 5.0, 3.0
    
    cout << "r1 area: " << r1.area() << endl;  // Output: 1.0
    cout << "r2 area: " << r2.area() << endl;  // Output: 5.0
    cout << "r3 area: " << r3.area() << endl;  // Output: 15.0
    
    return 0;
}

Copy Constructor

A copy constructor creates a new object as a copy of an existing object. It takes a reference to an object of the same class as a parameter.

class Point {
private:
    int x, y;
    
public:
    // Regular constructor
    Point(int a = 0, int b = 0) {
        x = a;
        y = b;
        cout << "Regular constructor called" << endl;
    }
    
    // Copy constructor
    Point(const Point &p) {
        x = p.x;
        y = p.y;
        cout << "Copy constructor called" << endl;
    }
    
    void display() {
        cout << "Point: (" << x << ", " << y << ")" << endl;
    }
};

When is the Copy Constructor Called?

The copy constructor is called in the following situations:

  1. When an object is created from another object:

    Point p1(10, 20);    // Regular constructor
    Point p2 = p1;       // Copy constructor
    Point p3(p1);        // Copy constructor
  2. When an object is passed by value to a function:

    void showPoint(Point p) {  // Copy constructor called for parameter
        p.display();
    }
    
    Point p1(10, 20);
    showPoint(p1);  // Copy constructor called here
  3. When a function returns an object by value:

    Point createPoint() {
        Point temp(5, 10);
        return temp;  // Copy constructor may be called here
    }
    
    Point p = createPoint();  // May invoke copy constructor

Compiler-provided Copy Constructor

If you don’t define a copy constructor, the compiler provides one that performs a member-by-member copy (shallow copy).

class Student {
private:
    string name;
    int rollNumber;
    
public:
    Student(string n, int r) {
        name = n;
        rollNumber = r;
    }
    
    // No copy constructor defined - compiler will provide one
    
    void display() {
        cout << "Name: " << name << ", Roll: " << rollNumber << endl;
    }
};

int main() {
    Student s1("John", 101);
    Student s2 = s1;  // Compiler's copy constructor used
    
    s2.display();  // Output: Name: John, Roll: 101
    return 0;
}

Deep Copy vs Shallow Copy

  1. Shallow Copy (default by compiler): Copies all member values as-is. If members are pointers, only the pointer addresses are copied, not the data they point to.

  2. Deep Copy (requires custom copy constructor): Creates new memory for pointer data and copies the values, not just the addresses.

class DeepCopyExample {
private:
    int* data;
    
public:
    // Regular constructor
    DeepCopyExample(int value) {
        data = new int;  // Allocate memory
        *data = value;
    }
    
    // Deep copy constructor
    DeepCopyExample(const DeepCopyExample &source) {
        data = new int;  // Allocate new memory
        *data = *(source.data);  // Copy the value
    }
    
    // Destructor
    ~DeepCopyExample() {
        delete data;  // Free memory
    }
    
    void setValue(int value) {
        *data = value;
    }
    
    void display() {
        cout << "Value: " << *data << endl;
    }
};

int main() {
    DeepCopyExample obj1(10);
    DeepCopyExample obj2 = obj1;  // Deep copy constructor called
    
    obj1.display();  // Output: Value: 10
    obj2.display();  // Output: Value: 10
    
    // Change obj2's data
    obj2.setValue(20);
    
    obj1.display();  // Output: Value: 10 (unchanged)
    obj2.display();  // Output: Value: 20 (changed)
    
    return 0;
}

Constructor Overloading

Constructor overloading allows a class to have multiple constructors with different parameter lists.

class Box {
private:
    double length, width, height;
    
public:
    // Default constructor
    Box() {
        length = width = height = 1.0;
    }
    
    // Constructor with one parameter
    Box(double side) {
        length = width = height = side;
    }
    
    // Constructor with three parameters
    Box(double l, double w, double h) {
        length = l;
        width = w;
        height = h;
    }
    
    double volume() {
        return length * width * height;
    }
};

int main() {
    Box box1;              // Uses default constructor
    Box box2(5.0);         // Uses constructor with one parameter
    Box box3(2.0, 3.0, 4.0); // Uses constructor with three parameters
    
    cout << "Volume of box1: " << box1.volume() << endl;  // Output: 1
    cout << "Volume of box2: " << box2.volume() << endl;  // Output: 125
    cout << "Volume of box3: " << box3.volume() << endl;  // Output: 24
    
    return 0;
}

Initialization Lists

A more efficient way to initialize class members is to use an initialization list:

class Person {
private:
    string name;
    int age;
    
public:
    // Using initialization list
    Person(string n, int a) : name(n), age(a) {
        cout << "Person created" << endl;
    }
    
    void display() {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
};

Initialization lists are especially important for:

  1. Const members (which must be initialized)
  2. Reference members (which must be initialized)
  3. Members without default constructors
  4. Better performance in some cases

Common Mistakes and Pitfalls

  1. Forgetting to initialize all members: Some members may have garbage values
  2. Redundant code in constructors: Duplicate initialization logic across constructors
  3. Not providing a default constructor: May cause issues when creating arrays or using containers
  4. Not handling dynamic memory properly: May lead to memory leaks

Summary

Constructors are special member functions that initialize objects when they are created. They ensure that objects start with a valid state. C++ supports different types of constructors: default constructors, parameterized constructors, and copy constructors. Understanding how to properly use constructors is essential for creating well-designed and robust C++ classes.