Classes and their objects

Instance members

Instance members in a C++ class are elements (variables or functions) that belong to instances of the class, rather than to the class itself. Each object (instance) of the class has its own separate copy of these members. There are two main types of instance members in a C++ class:

Instance Variables (Non-static Data Members): These are the variables defined in a class that are not declared as static. Each object of the class will have its own copy of these variables. They represent the state of an object.

Instance Methods (Non-static Member Functions): These are the functions defined in a class that are not declared as static. These methods operate on the instance variables of the object that invokes them.

Here’s an example to illustrate instance members in a C++ class:

C++
class Car {
    private:
        // Instance variable
        int speed;

    public:
        // Constructor
        Car() : speed(0) {}

        // Instance method
        void accelerate(int increment) {
            speed += increment;
        }

        // Another instance method
        int getSpeed() const {
            return speed;
        }
};

int main() {
    Car myCar; // Creating an object of Car
    myCar.accelerate(10); // Calling an instance method
    int currentSpeed = myCar.getSpeed(); // Using another instance method
    return 0;
}

In this example, speed is an instance variable, and accelerate and getSpeed are instance methods of the Car class. Each Car object will have its own speed, and the methods accelerate and getSpeed will operate on the speed of the Car object that calls them.

Class Members (Static)

Class members in C++ that are declared as static belong to the class itself, rather than to any particular instance of the class. This means that they are shared among all instances of the class. Static members can include both variables and functions.

Here are the key points about static members in C++:

Static Variables (Class Variables): These are variables declared with the static keyword within a class. They are shared by all objects of the class. All instances of the class access the same static variable, and any changes made to the variable are reflected across all instances.

Static Methods (Class Methods): These are functions declared as static within a class. They can be called without creating an instance of the class. Static methods can only access static variables or other static methods; they cannot access non-static members of the class.

Here’s an example to illustrate static members in a C++ class:

C++
#include <iostream>

class Car {
    private:
        static int totalCars; // Static variable

    public:
        // Constructor
        Car() {
            totalCars++; // Increment total cars
        }

        // Static method
        static int getTotalCars() {
            return totalCars;
        }
};

// Initialize the static member
int Car::totalCars = 0;

int main() {
    Car car1; // First instance of Car
    Car car2; // Second instance of Car

    // Access static method
    std::cout << "Total cars: " << Car::getTotalCars() << std::endl;

    return 0;
}

Virtual Functions

Virtual functions in C++ are a fundamental concept for achieving runtime polymorphism. They allow a function in a base class to be overridden in a derived class, enabling dynamic method binding.

Definition: A virtual function is a function in a base class that is declared using the keyword virtual. When a function is marked as virtual, it can be overridden in any derived class. The version of the function that gets executed is determined at runtime based on the type of the object that invokes the function.

Runtime Polymorphism

  • How It Works: When you have a pointer or a reference to a base class object, it can point to objects of the derived class as well. Calling a virtual function through this pointer or reference will execute the function defined in the derived class, if it is overridden there.

Example:

Consider an example with a base class Animal and derived classes Dog and Cat:

C++
#include <iostream>
using namespace std;

class Animal {
public:
    // Virtual function
    virtual void speak() {
        cout << "Some animal sound" << endl;
    }
};

class Dog : public Animal {
public:
    // Overriding the virtual function
    void speak() override {
        cout << "Woof Woof" << endl;
    }
};

class Cat : public Animal {
public:
    // Overriding the virtual function
    void speak() override {
        cout << "Meow Meow" << endl;
    }
};

int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();

    animal1->speak();  // Outputs: Woof Woof
    animal2->speak();  // Outputs: Meow Meow

    delete animal1;    // Clean up
    delete animal2;    // Clean up
    return 0;
}

In this example:

  • Animal has a virtual function speak().
  • Dog and Cat override this function.
  • When speak() is called on an Animal pointer that points to a Dog or Cat object, the corresponding overridden function in the Dog or Cat class is executed.
  • This demonstrates runtime polymorphism: the decision about which function to execute is made at runtime based on the actual type of the object that the Animal pointer is referring to.

Key Points

  • Virtual functions enable runtime polymorphism, allowing different behaviors for objects of derived classes when accessed through a base class reference or pointer.
  • The virtual function mechanism is central to achieving dynamic method binding in C++, which is the ability to decide which function to invoke at runtime rather than at compile time.

Pure Virtual Functions

Definition: A pure virtual function is a function that is declared in a base class but must be implemented in a derived class. It is declared by assigning 0 to the function declaration in the base class. Pure virtual functions are used to create abstract classes in C++, which are classes that cannot be instantiated directly and are meant to be inherited.

Purpose

  • Creating Abstract Base Classes: Pure virtual functions are used to define an interface in the base class. Any class that inherits from this base class must provide an implementation for these functions, ensuring a consistent interface across derived classes.
  • Ensuring Implementation in Derived Classes: They are essential for scenarios where the base class cannot provide a meaningful implementation for a function, and the implementation must be provided by the derived classes.

Example:

C++
#include <iostream>
using namespace std;

class Shape {
public:
    // Pure virtual function
    virtual void draw() = 0;
};

class Circle : public Shape {
public:
    void draw() override {
        cout << "Drawing Circle" << endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() override {
        cout << "Drawing Rectangle" << endl;
    }
};

int main() {
    Shape* shape1 = new Circle();
    Shape* shape2 = new Rectangle();

    shape1->draw();  // Outputs: Drawing Circle
    shape2->draw();  // Outputs: Drawing Rectangle

    delete shape1;   // Clean up
    delete shape2;   // Clean up
    return 0;
}

In this example:

  • Shape is an abstract base class with a pure virtual function draw().
  • Circle and Rectangle are derived classes that provide specific implementations of the draw() function.

Differences between Virtual Functions and Pure Virtual Functions

  • Default Implementation:
    • Virtual Function: Can have a default implementation in the base class. It’s optional for the derived class to override it.
    • Pure Virtual Function: Does not have a default implementation in the base class. It must be overridden by the derived class.
  • Abstract Class Creation:
    • Virtual Function: The presence of a virtual function does not make a class abstract. The class can still be instantiated.
    • Pure Virtual Function: If a class has at least one pure virtual function, it becomes an abstract class and cannot be instantiated.
  • Use Case:
    • Virtual Function: Used when the base class has a default behavior that can be shared or overridden by derived classes.
    • Pure Virtual Function: Used when the base class defines only an interface, and the actual implementation is mandatory in each derived class.

In summary, while virtual functions allow derived classes to override a method, pure virtual functions require it, making them a key tool in defining abstract interfaces in C++.

Abstract Classes

Definition

An abstract class in C++ is a class that is designed to be specifically used as a base class. An abstract class contains at least one pure virtual function. It represents a high-level concept that cannot be instantiated on its own.

Characteristics

  • Cannot Be Instantiated: You cannot create objects of an abstract class.
  • Intended for Inheritance: Abstract classes are intended to be inherited by concrete classes that implement the pure virtual functions.
  • Defines Interface: They are often used to define an interface for other classes to follow.

Purpose

  • Enforcing a Contract for Derived Classes: By declaring at least one pure virtual function, an abstract class ensures that any derived class must implement these functions, providing a consistent interface.
  • Code Reusability: Abstract classes can also contain normal (non-pure) virtual functions with implementation, promoting code reuse.

Example:

Scenario:

We want to model a system where different types of vehicles can be represented. All vehicles should have a method to start, but the specific way they start depends on the type of vehicle.

C++
#include <iostream>
using namespace std;

// Abstract class
class Vehicle {
public:
    // Pure virtual function
    virtual void startEngine() = 0;

    // A normal member function with implementation
    void honk() {
        cout << "Honk! Honk!" << endl;
    }
};

class Car : public Vehicle {
public:
    void startEngine() override {
        cout << "Car engine started" << endl;
    }
};

class Boat : public Vehicle {
public:
    void startEngine() override {
        cout << "Boat engine started" << endl;
    }
};

class Bicycle : public Vehicle {
public:
    void startEngine() override {
        cout << "Bicycle has no engine!" << endl;
    }
};

int main() {
    // Vehicle v; // Error: Cannot instantiate an abstract class
    Vehicle* car = new Car();
    Vehicle* boat = new Boat();
    Vehicle* bicycle = new Bicycle();

    car->startEngine();     // Outputs: Car engine started
    boat->startEngine();    // Outputs: Boat engine started
    bicycle->startEngine(); // Outputs: Bicycle has no engine!
    
    car->honk();            // Outputs: Honk! Honk!
    boat->honk();           // Outputs: Honk! Honk!
    bicycle->honk();        // Outputs: Honk! Honk!

    delete car;             // Clean up
    delete boat;            // Clean up
    delete bicycle;         // Clean up
    return 0;
}

In this example:

  • Vehicle is an abstract class with a pure virtual function startEngine().
  • CarBoat, and Bicycle are derived classes that provide specific implementations of startEngine().
  • The honk() method is a regular member function of Vehicle, which can be used by all derived classes.
  • We cannot instantiate Vehicle directly, as demonstrated by the commented line, but we can instantiate CarBoat, and Bicycle and call both startEngine() and honk() on them.

Key Points

  • This example demonstrates how an abstract class can be used to define a common interface (startEngine()) for various derived classes.
  • It also shows how abstract classes can contain non-pure virtual functions (honk()) that provide default behavior.
  • The abstract class Vehicle serves as a blueprint for creating specific types of vehicles with their unique engine start behaviors, while also sharing common functionality like honk().