Upcasting and Downcasting in CPP

Introduction

Imagine you’re playing a video game where you can choose different characters. Each character has unique abilities, but the game controls remain the same. This is similar to upcasting in C++, where a derived class object can be treated as a base class object. Now, imagine you want to use a special ability of a specific character. This is similar to downcasting in C++, where a base class object can be treated as a derived class object.

Upcasting And Downcasting In Cpp
Upcasting and Downcasting

What Is Upcasting in C++?

Upcasting in C++ refers to the process of converting a derived class pointer or reference to a base class pointer or reference. It’s a fundamental concept in object-oriented programming that allows you to treat a derived class object as if it were an object of its base class. This is possible because a derived class inherits all the members (both data and functions) of its base class.

Here’s why upcasting is useful: imagine you have a hierarchy of classes, with a base class at the top and multiple derived classes extending it. Each derived class may have its own unique features, but they also share common properties and behaviors from the base class. Upcasting lets you access those shared properties and behaviors without having to write separate code for each derived class.

Syntax for Upcasting:

C++
BaseClass *basePtr = &derivedObject;

Here’s a simple example to illustrate upcasting:

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

class Animal {
public:
    virtual void speak() {
        cout << "Animal speaks!" << endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        cout << "Dog barks!" << endl;
    }
};

int main() {
    Dog dog;
    Animal *animalPtr = &dog; // Upcasting

    animalPtr->speak(); // Output: Dog barks!

    return 0;
}

Output:

C++
Dog barks!

Explanation:

  • The example involves two classes: Animal and Dog, with Dog inheriting from the Animal.
  • The Dog class overrides the speak function, providing its own implementation.
  • A Dog object is created and upcasted to an Animal pointer (Animal *animalPtr = &dog;).
  • Despite upcasting, calling the speak function on animalPtr triggers the overridden Dog’s function.
  • The output displays “Dog barks!” due to the polymorphic behavior of upcasting.

What Is Downcasting in C++?

Downcasting in C++: Understanding Casting Objects

Downcasting in C++ is the process of converting a base class pointer or reference to a derived class pointer or reference. This is a bit more intricate than upcasting, as it involves going from a more general class to a more specific one. Downcasting can be useful when you have a base class pointer that’s actually pointing to a derived class object, and you want to access the derived class’s specific members.

Why Use Downcasting:

The primary purpose of downcasting is to gain access to the derived class’s unique attributes and functionalities that aren’t available in the base class. When you perform upcasting, you can’t directly use these derived class features. Downcasting helps you tap into those specific capabilities.

Syntax for Downcasting:

To perform downcasting, you use the type casting operator along with the derived class’s name in parentheses before the pointer or reference to the base class object. If the casting is possible, you can then access the derived class members.

C++
DerivedClass* derivedPtr = dynamic_cast(basePtr);

Code Example and Output:

C++
#include <iostream>

class Base {
public:
    virtual void display() {
        std::cout << "Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void display() override {
        std::cout << "Derived class" << std::endl;
    }

    void specialFunction() {
        std::cout << "Special function in Derived class" << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived(); // Upcasting: Base pointer points to Derived object

    // Downcasting
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);

    if (derivedPtr) {
        derivedPtr->display(); // Calls Derived class's function
        derivedPtr->specialFunction(); // Calls Derived class's unique function
    } else {
        std::cout << "Downcasting failed" << std::endl;
    }

    delete basePtr;
    return 0;
}

Output:

C++
Derived class
Special function in Derived class

Points to Remember:

  • Downcasting is converting a base class pointer or reference to a derived class pointer or reference.
  • It helps access the specific members and functionalities of the derived class.
  • Dynamic casting is used for downcasting.
  • The syntax involves the dynamic_cast operator with the derived class’s type.
  • Always check the result of downcasting using an if condition.
  • If successful, you can use the derived class’s features through the downcasted pointer or reference.

Understand by Detailed Diagram

Upcasting And Downcasting In C++
  • Start with a base class ‘Base’ and a derived class ‘Derived’ that inherits from ‘Base’.
  • Upcasting: Create a pointer of type ‘Base’ and assign it to an object of type ‘Derived’. This is Upcasting.
  • Perform some operation using the ‘Base’ pointer.
  • Downcasting: Convert the ‘Base’ pointer back to a ‘Derived’ pointer using dynamic_cast or static_cast. This is Downcasting.
  • Perform some operation using the ‘Derived’ pointer.
  • End.

A Problem to Solve

Problem Statement:

You are given a base class Vehicle and two derived classes Car and Bicycle. Your task is to create these classes and demonstrate upcasting and downcasting by implementing the following functionalities:

  1. Upcasting: Create a function that takes a pointer to a Vehicle and calls a virtual function displayType() to display the type of vehicle. Test this function with instances of Car and Bicycle to demonstrate upcasting.
  2. Downcasting: Create a function that takes a pointer to a Vehicle and downcasts it to the appropriate derived class (Car or Bicycle) using dynamic_cast. Then, call a member function specific to the derived class to prove that the downcasting was successful.

Details:

  • Base Class (Vehicle): This class should have a virtual function displayType() that prints “This is a generic vehicle.”.
  • Derived Class (Car): This class should inherit from Vehicle and override the displayType() function to print “This is a car.”. It should also have a function showCarDetails() that prints “Car-specific details.”.
  • Derived Class (Bicycle): This class should inherit from Vehicle and override the displayType() function to print “This is a bicycle.”. It should also have a function showBicycleDetails() that prints “Bicycle-specific details.”.

Hints:

  • You can use the dynamic_cast operator to safely downcast from a base class pointer to a derived class pointer.
  • Make sure that you make the base class functions you want to override in derived classes virtual to enable dynamic dispatch.

Expected Output:

The output from your testing functions should demonstrate that upcasting allows the appropriate derived class function to be called through a base class pointer, and that downcasting allows you to call functions specific to a derived class from a base class pointer.

Examples of Using Upcasting and Downcasting in C++

Let’s look at some examples to see how upcasting and downcasting can be used in C++. We’ll provide the code, the expected output, and a step-by-step explanation of how upcasting and downcasting are used.

Example 1

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

class Animal {
public:
    virtual void makeSound() { cout << "The animal makes a sound.\n"; }
};

class Dog: public Animal {
public:
    void makeSound() { cout << "The dog barks.\n"; }
};

int main() {
    Animal* animal1 = new Animal;
    Animal* animal2 = new Dog;
    animal1->makeSound();  // Output: The animal makes a sound.
    animal2->makeSound();  // Output: The dog barks.
    return 0;
}

Output:

C++
The animal makes a sound 
The dog barks 

Explanation:

  • Class Definitions: Define “Animal” and “Dog” classes.
  • Function Definitions: Both classes have a “makeSound()” function. “Animal” declares it as virtual, while “Dog” overrides it with the specific sound of a dog barking.
  • Main Function: Create pointers “animal1” and “animal2” of type “Animal” and assign them new “Animal” and “Dog” objects.
  • Function Calls: Invoke “makeSound()” on “animal1” and “animal2” pointers. Polymorphism enables the appropriate “makeSound()” function based on the object’s actual type.
  • Output: Print the generic sound of an animal and the sound of a dog barking, showcasing polymorphic behavior.

Example 2

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

class Animal {
public:
    virtual void makeSound() { cout << "The animal makes a sound." << endl; }
};

class Dog: public Animal {
public:
    void makeSound() { cout << "The dog barks." << endl; }
    void wagTail() { cout << "The dog wags its tail." << endl; }
};

int main() {
    Animal* animal = new Dog;             // Create a pointer to Animal pointing to a Dog object
    Dog* dog = dynamic_cast<Dog*>(animal); // Cast the Animal pointer to Dog pointer

    if (dog) {
        dog->wagTail();                   // Call the wagTail() function using the Dog pointer
    } else {
        cout << "Dynamic cast failed." << endl;
    }

    delete animal;                        // Delete the allocated memory
    return 0;
}

Output:

C++
The dog wags its tail 

Explanation:

  • Class Definitions: Define “Animal” and “Dog” classes. “Dog” is derived from “Animal” and has an additional function, “wagTail()”.
  • Function Definitions: Both classes have a “makeSound()” function. “Animal” defines it as virtual, and “Dog” overrides it with the sound of a dog barking. “Dog” also defines the “wagTail()” function.
  • Main Function: Create an “Animal” pointer, “animal,” and assign it a new “Dog” object.
  • Type Casting: Perform a dynamic cast of the “animal” pointer to a “Dog” pointer, “dog.”
  • Function Call: Call “dog->wagTail()” to invoke the “wagTail()” function. Print “The dog wags its tail” to demonstrate accessing the specific behavior of a dog using dynamic casting.

Example 3

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

class Animal {
public:
    virtual void makeSound() { cout << "The animal makes a sound\n"; }
};

class Dog: public Animal {
public:
    void makeSound() { cout << "The dog barks\n"; }
    void wagTail() { cout << "The dog wags its tail\n"; }
};

int main() {
    Animal* animal = new Animal;  // Creating an Animal object
    Dog* dog = dynamic_cast<Dog*>(animal);  // Attempting to downcast Animal to Dog
    
    if (dog) {
        dog->wagTail();  // If successful, calling Dog's wagTail function
    } else {
        cout << "Downcasting failed\n";  // If downcasting fails, this message is printed
    }
    
    delete animal;  // Deleting the dynamically allocated object
    return 0;
}

Output:

C++
Downcasting failed 

Explanation:

  • Class Definitions: Define “Animal” and “Dog” classes. “Dog” is derived from “Animal” and has a “makeSound()” function and a “wagTail()” function.
  • Main Function: Create an “Animal” pointer, “animal,” pointing to a new “Animal” object.
  • Type Casting: Attempt to dynamically cast the “animal” pointer to a “Dog” pointer, “dog.”
  • Conditional Check: Check if the dynamic cast succeeded by evaluating the “dog” pointer. Since the cast fails, print “Downcasting failed.”
  • End: Program execution completes.

The Pros and Cons of Using Upcasting and Downcasting

AspectUpcastingDowncasting
DefinitionConverting a derived class pointer/reference to a base class type.Converting a base class pointer/reference to a derived class type.
UsageCommonly used and safer.Less common, requires caution.
SafetySafer, as upcasting maintains base class behavior.Riskier, may lead to runtime errors if not done carefully.
FunctionalityLimited access to base class members only.Full access to derived class members.
Compile-Time CheckCompile-time checked, less prone to errors.No compile-time check, may lead to runtime errors.
ExampleBase *basePtr = new Derived();Derived *derivedPtr = dynamic_cast(basePtr);
PolymorphismSupports polymorphism as it maintains base class properties.Maintains polymorphism and accesses derived class features.
The Pros and Cons of Using Upcasting and Downcasting

Key Takeaways

  • Upcasting: Upcasting involves treating a derived class object as its base class type. It allows you to use the object in a more general manner.
  • Downcasting: Downcasting is the opposite of upcasting. It involves treating a base class pointer/reference as its derived class type. It’s used when you want to access specific features of the derived class.
  • General to Specific: Upcasting lets you treat objects generally, which is useful for polymorphism and organizing code.
  • Specific to General: Downcasting is useful when you need to access specialized features of a derived class while using a base class reference/pointer.
  • Flexibility and Safety: Upcasting enhances flexibility while downcasting requires type checks to ensure safety, which may involve some runtime overhead.

Conclusion

In the world of C++, upcasting and downcasting are tools that help you work with objects in different ways. Imagine you have a “special” box that can turn into a “normal” box and vice versa. Upcasting is like using the special box as a normal one – you can’t see its special features anymore, but it’s easier to work with in some situations. Downcasting is like turning the normal box back into a special one – you can access its unique features again.

Understanding how to use these tools is super important. They help you write programs that can handle different situations easily. For example, you can make a program that works for many different shapes, even if you don’t know exactly what shape it will be. By practicing these concepts, you’ll become really good at making your programs more flexible and better at adapting to different needs. So keep practicing, and soon you’ll be a pro at using upcasting and downcasting!

FAQs

  1. What is upcasting and downcasting in C++?
    • Upcasting in C++ is a process where a pointer or a reference of a derived class is converted into a pointer or a reference of its base class. Downcasting, on the other hand, is a process where a pointer or a reference of a base class is converted into a pointer or a reference of its derived class.
  2. Why do we use upcasting and downcasting in C++?
    • We use upcasting in C++ to treat a derived class object as a base class object. This is particularly useful when dealing with inheritance and polymorphism. We use downcasting to access the specific properties or methods of a derived class using a base class pointer or reference.
  3. How do we use upcasting and downcasting in C++?
    • We use upcasting when we want to use a derived class object wherever a base class object is expected. We use downcasting when we want to access the specific properties or methods of a derived class using a base class pointer or reference.
  4. Can using upcasting and downcasting make code more confusing?
    • If used incorrectly, upcasting and downcasting can make your code less efficient or lead to runtime errors. It’s important to understand how upcasting and downcasting work and when to use them.
  5. What are some examples of using upcasting and downcasting in C++?
    • Some examples include treating a derived class object as a base class object (upcasting) and accessing the specific properties or methods of a derived class using a base class pointer or reference (downcasting).
Avatar Of Deepak Vishwakarma
Deepak Vishwakarma

Founder

RELATED Articles

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.