BogoToBogo
  • Home
  • About
  • Big Data
  • Machine Learning
  • AngularJS
  • Python
  • C++
  • go
  • DevOps
  • Kubernetes
  • Algorithms
  • More...
    • Qt 5
    • Linux
    • FFmpeg
    • Matlab
    • Django 1.8
    • Ruby On Rails
    • HTML5 & CSS

Upcasting and Downcasting - 2020

cplusplus_icon.png




Bookmark and Share





bogotobogo.com site search:




Upcasting and Downcasting

Upcasting

Upcasting is converting a derived-class reference or pointer to a base-class. In other words, upcasting allows us to treat a derived type as though it were its base type. It is always allowed for public inheritance, without an explicit type cast. This is a result of the is-a relationship between the base and derived classes.


Here is the code dealing with shapes. We created Shape class, and derived Circle, Square, and Triangle classes from the Shape class. Then, we made a member function that talks to the base class:

void play(Shape& s) 
{
   s.draw();
   s.move();
   s.shrink();
   ....
}

The function speaks to any Shape, so it is independent of the specific type of object that it's drawing, moving, and shrinking. If in some other part of the program we use the play( ) function like below:

Circle c;
Triangle t;
Square sq;
play(c);
play(t);
play(sq);

Let's check what's happening here. A Triangle is being passed into a function that is expecting a Shape. Since a Triangle is a Shape, it can be treated as one by play(). That is, any message that play() can send to a Shape a Triangle can accept.

Upcasting allows us to treat a derived type as though it were its base type. That's how we decouple ourselves from knowing about the exact type we are dealing with.

Note that it doesn't say "If you're a Triangle, do this, if you're a Circle, do that, and so on." If we write that kind of code, which checks for all the possible types of a Shape, it will soon become a messy code, and we need to change it every time we add a new kind of Shape. Here, however, we just say "You're a Shape, I know you can move(), draw(), and shrink( ) yourself, do it, and take care of the details correctly."

The compiler and runtime linker handle the details. If a member function is virtual, then when we send a message to an object, the object will do the right thing, even when upcasting is involved.

Note that the most important aspect of inheritance is not that it provides member functions for the new class, however. It's the relationship expressed between the new class and the base class. This relationship can be summarized by saying, "The new class is a type of the existing class."

class Parent {
public:
  void sleep() {}
};

class Child: public Parent {
public:
  void gotoSchool(){}
};

int main( ) 
{ 
  Parent parent;
  Child child;

  // upcast - implicit type cast allowed
  Parent *pParent = &child; 

  // downcast - explicit type case required 
  Child *pChild =  (Child *) &parent;

  pParent -> sleep();
  pChild -> gotoSchool();
    
  return 0; 
}

A Child object is a Parent object in that it inherits all the data members and member functions of a Parent object. So, anything that we can do to a Parent object, we can do to a Child object. Therefore, a function designed to handle a Parent pointer (reference) can perform the same acts on a Child object without any problems. The same idea applies if we pass a pointer to an object as a function argument. Upcasting is transitive: if we derive a Child class from Parent, then Parent pointer (reference) can refer to a Parent or a Child object.

Upcasting can cause object slicing when a derived class object is passed by value as a base class object, as in foo(Base derived_obj).




Downcasting

The opposite process, converting a base-class pointer (reference) to a derived-class pointer (reference) is called downcasting. Downcasting is not allowed without an explicit type cast. The reason for this restriction is that the is-a relationship is not, in most of the cases, symmetric. A derived class could add new data members, and the class member functions that used these data members wouldn't apply to the base class.

As in the example, we derived Child class from a Parent class, adding a member function, gotoSchool(). It wouldn't make sense to apply the gotoSchool() method to a Parent object. However, if implicit downcasting were allowed, we could accidentally assign the address of a Parent object to a pointer-to-Child

Child *pChild =  &parent; // actually this won't compile
        // error: cannot convert from 'Parent *' to 'Child *'

and use the pointer to invoke the gotoSchool() method as in the following line.

pChild -> gotoSchool();

Because a Parent isn't a Child (a Parent need not have a gotoSchool() method), the downcasting in the above line can lead to an unsafe operation.

C++ provides a special explicit cast called dynamic_cast that performs this conversion. Downcasting is the opposite of the basic object-oriented rule, which states objects of a derived class, can always be assigned to variables of a base class.

One more thing about the upcasting:
Because implicit upcasting makes it possible for a base-class pointer (reference) to refer to a base-class object or a derived-class object, there is the need for dynamic binding. That's why we have virtual member functions.

  1. Pointer (Reference) type: known at compile time.
  2. Object type: not known until run time.





Dynamic Casting

The dynamic_cast operator answers the question of whether we can safely assign the address of an object to a pointer of a particular type.

Here is a similar example to the previous one.

#include <string>

class Parent {
public:
  void sleep() {
  }
};

class Child: public Parent {
private:
  std::string classes[10];
public:
  void gotoSchool(){}
};

int main( ) 
{ 
  Parent *pParent = new Parent;
  Parent *pChild = new Child;
    
  Child *p1 = (Child *) pParent;  // #1
  Parent *p2 = (Child *) pChild;  // #2
  return 0; 
}

Let look at the lines where we do type cast.

Child *p1 = (Child *) pParent;  // #1
Parent *p2 = (Child *) pChild;  // #2

Which of the type cast is safe?
The only one guaranteed to be safe is the ones in which the pointer is the same type as the object or else a base type for the object.

Type cast #1 is not safe because it assigns the address of a base-class object (Parent) to a derived class (Child) pointer. So, the code would expect the base-class object to have derived class properties such as gotoSchool() method, and that is false. Also, Child object, for example, has a member classes that a Parent object is lacking.

Type case #2, however, is safe because it assigns the address of a derived-class object to a base-class pointer. In other words, public derivation promises that a Child object is also a Parent object.

The question of whether a type conversion is safe is more useful than the question of what kind of object is pointed to. The usual reason for wanting to know the type is so that we can know if it's safe to invoke a particular method.

Here is the syntax of dynamic_cast.

Child *p = dynamic_cast<Child *>(pParent)

This code is asking whether the pointer pParent can be type cast safely to the type Child *.

  1. It returns the address of the object, if it can.
  2. It returns 0, otherwise.

How do we use the dynamic_cast?

void f(Parent* p) {
  Child *ptr = dynamic_cast<Child*>(p);
   if(ptr) { 
    // we can safely use ptr
  } 
}

In the code, if (ptr) is of the type Child or else derived directly or indirectly from the type Child, the dynamic_cast converts the pointer p to a pointer of type Child. Otherwise, the expression evaluates to 0, the null pointer.

In other words, we want to check if we can use the passed in pointer p before we do some operation on a child class object even though it's a pointer to base class.

"The need for dynamic_cast generally arises because we want perform derived class operation on a derived class object, but we have only a pointer-or reference-to-base." -Scott Meyers









Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization

YouTubeMy YouTube channel

Sponsor Open Source development activities and free contents for everyone.

Thank you.

- K Hong






Sponsor Open Source development activities and free contents for everyone.

Thank you.

- K Hong






C++ Tutorials

C++ Home

Algorithms & Data Structures in C++ ...

Application (UI) - using Windows Forms (Visual Studio 2013/2012)

auto_ptr

Binary Tree Example Code

Blackjack with Qt

Boost - shared_ptr, weak_ptr, mpl, lambda, etc.

Boost.Asio (Socket Programming - Asynchronous TCP/IP)...

Classes and Structs

Constructor

C++11(C++0x): rvalue references, move constructor, and lambda, etc.

C++ API Testing

C++ Keywords - const, volatile, etc.

Debugging Crash & Memory Leak

Design Patterns in C++ ...

Dynamic Cast Operator

Eclipse CDT / JNI (Java Native Interface) / MinGW

Embedded Systems Programming I - Introduction

Embedded Systems Programming II - gcc ARM Toolchain and Simple Code on Ubuntu and Fedora

Embedded Systems Programming III - Eclipse CDT Plugin for gcc ARM Toolchain

Exceptions

Friend Functions and Friend Classes

fstream: input & output

Function Overloading

Functors (Function Objects) I - Introduction

Functors (Function Objects) II - Converting function to functor

Functors (Function Objects) - General



Git and GitHub Express...

GTest (Google Unit Test) with Visual Studio 2012

Inheritance & Virtual Inheritance (multiple inheritance)

Libraries - Static, Shared (Dynamic)

Linked List Basics

Linked List Examples

make & CMake

make (gnu)

Memory Allocation

Multi-Threaded Programming - Terminology - Semaphore, Mutex, Priority Inversion etc.

Multi-Threaded Programming II - Native Thread for Win32 (A)

Multi-Threaded Programming II - Native Thread for Win32 (B)

Multi-Threaded Programming II - Native Thread for Win32 (C)

Multi-Threaded Programming II - C++ Thread for Win32

Multi-Threaded Programming III - C/C++ Class Thread for Pthreads

MultiThreading/Parallel Programming - IPC

Multi-Threaded Programming with C++11 Part A (start, join(), detach(), and ownership)

Multi-Threaded Programming with C++11 Part B (Sharing Data - mutex, and race conditions, and deadlock)

Multithread Debugging

Object Returning

Object Slicing and Virtual Table

OpenCV with C++

Operator Overloading I

Operator Overloading II - self assignment

Pass by Value vs. Pass by Reference

Pointers

Pointers II - void pointers & arrays

Pointers III - pointer to function & multi-dimensional arrays

Preprocessor - Macro

Private Inheritance

Python & C++ with SIP

(Pseudo)-random numbers in C++

References for Built-in Types

Socket - Server & Client

Socket - Server & Client 2

Socket - Server & Client 3

Socket - Server & Client with Qt (Asynchronous / Multithreading / ThreadPool etc.)

Stack Unwinding

Standard Template Library (STL) I - Vector & List

Standard Template Library (STL) II - Maps

Standard Template Library (STL) II - unordered_map

Standard Template Library (STL) II - Sets

Standard Template Library (STL) III - Iterators

Standard Template Library (STL) IV - Algorithms

Standard Template Library (STL) V - Function Objects

Static Variables and Static Class Members

String

String II - sstream etc.

Taste of Assembly

Templates

Template Specialization

Template Specialization - Traits

Template Implementation & Compiler (.h or .cpp?)

The this Pointer

Type Cast Operators

Upcasting and Downcasting

Virtual Destructor & boost::shared_ptr

Virtual Functions



Programming Questions and Solutions ↓

Strings and Arrays

Linked List

Recursion

Bit Manipulation

Small Programs (string, memory functions etc.)

Math & Probability

Multithreading

140 Questions by Google



Qt 5 EXPRESS...

Win32 DLL ...

Articles On C++

What's new in C++11...

C++11 Threads EXPRESS...

Go Tutorial

OpenCV...








Contact

BogoToBogo
contactus@bogotobogo.com

Follow Bogotobogo

About Us

contactus@bogotobogo.com

YouTubeMy YouTube channel
Pacific Ave, San Francisco, CA 94115

Pacific Ave, San Francisco, CA 94115

Copyright © 2024, bogotobogo
Design: Web Master