Constructors and Destructors

Constructors and destructors are special methods of a class that are automatically called when an object is created or deleted. They allow you to initialize the object's attributes and perform clean-up tasks.

Constructors

A constructor is a member function of a class with the same name as the class, which is used to initialize the object's attributes when it is created. It does not have any return type, not even void. You can think of constructors as "blueprints" that guide how objects should be set up initially.

Constructors can be overloaded by specifying different numbers or types of parameters. However, if you do not define any constructor in your class, the compiler will automatically provide a default constructor with no parameters.

Example

#include <iostream>

class House {
public:
    std::string color;

    // Default constructor
    House() {
        this->color = "Blue";
    }

    // Parameterized constructor
    House(std::string color) {
        this->color = color;
    }

    void printColor() {
        std::cout << "This house is: " << color << std::endl;
    }
};

int main() {
    House house1;
    House house2("Red"); // Calls parameterized constructor

    house1.printColor();
    house2.printColor();

    return 0;
}

In this example, we have defined two constructors for the House class: one without parameters (default) and another one with two parameters. When we create objects house1 and house2, each calls a different constructor based on the provided arguments.

Destructors

A destructor is another special member function of a class that gets automatically called when an object goes out of scope or is explicitly deleted. It also has the same name as the class, but it is preceded by a tilde ~. Destructors are used to perform clean-up tasks, such as releasing memory or closing files.

Unlike constructors, destructors cannot be overloaded and you can have only one destructor per class.

Example

#include <iostream>

class House {
public:
    std::string color;

    // Default constructor
    House() {
        this->color = "Blue";
    }

    // Parameterized constructor
    House(std::string color) {
        this->color = color;
    }

    ~House() {
        std::cout << color << " house has been destroyed" << std::endl;
    }

    void printColor() {
        std::cout << "This house is: " << color << std::endl;
    }
};

int main() {
    {
        House house1;
        house1.printColor();
        // Do stuff here...
    } // Destructor called when going out of scope.


    return 0;
}

In this example, we defined a destructor for the House class. When the object house1 goes out of scope (at the end of the inner block), its destructor is automatically called.

Initializer Lists

Initializer lists are used to initialize member variables before the body of the constructor is executed. They can improve performance by preventing unnecessary default initialization and assignment operations.

You can use initializer lists in constructors by appending them after the colon : and before the opening brace {.

class Dog {
public:
    std::string name;
    int age;

    // Constructor with initializer list
    Dog(std::string n, int a) : name(n), age(a) {}
};

In this example, we use an initializer list in the Dog constructor to directly initialize name and age without requiring any additional assignment statements.

Copy Constructors

A copy constructor is a special type of constructor used to create a new object as a copy of an existing one. It takes a reference to the same class as its parameter.

If you don't define a copy constructor, the compiler will generate one automatically for you. However, sometimes you may need to define your own custom copy constructor.

Example

#include <iostream>

class Dog {
public:
    std::string name;
    int age;

    // Default constructor
    Dog(std::string n, int a) : name(n), age(a) {}

    // Copy constructor
    Dog(const Dog& other) {
        name = other.name;
        age = other.age;
        std::cout << "A dog named " << name << " has been cloned!" << std::endl;
    }
};

int main() {
    Dog dog1("Fido", 3);
    Dog dog2(dog1); // Calls copy constructor

    return 0;
}

Deleting Constructors

In some cases, you might want to prevent certain types of constructors from being generated by the compiler. For instance, if you want to prevent a class from being copied, you can delete the copy constructor and assignment operator.

Example

#include <string>

class Dog {
public:
    std::string name;
    int age;

    // Default constructor
    Dog(std::string n, int a) : name(n), age(a) {}

    // Delete copy constructor and assignment operator
    Dog(const Dog&) = delete;
    Dog& operator=(const Dog&) = delete;
};

int main() {
    Dog dog1("Fido", 3);
    // The following lines (each) will cause a compile-time error.
    Dog dog2(dog1); 
    Dog dog3 = dog1;

    return 0;
}

In this example, we've deleted the copy constructor and assignment operator for the Dog class. This will prevent instances of this class from being copied or assigned to one another.

Header files

In C++, it is common practice to separate class declarations from their definitions. This is done using header files (with the .h or .hpp extension) for declarations and source files (with the .cpp extension) for definitions.

However, to keep things simple in this guide, we will declare and define our classes within a single file. Although this is not recommended for large projects, it's perfectly fine when learning or working on small programs.

Example

Car.hpp

#include <string>

class Car {
public:
    Car(const std::string& brand, int year);
    
    void honk();
   
private:
    std::string brand;
    int year;
};

Car.cpp

#include "Car.hpp"
#include <iostream>

Car::Car(const std::string& brand, int year)
  : brand(brand), year(year) {}

void Car::honk() {
    std::cout << "Honk! I'm a " << brand << " car from " << year << ".\n";
}

In this guide, we will continue to declare and define classes within the same file for simplicity. However, keep in mind that it is good practice to separate them as shown above when working on real-world projects.