Abstract vs Concrete Class

Abstraction 

  • Enabling us to ignore the inner details of data structure/feature/functionality and focus on the higher-level functionality is the power of abstraction. 
  • It is one of the Object Oriented Concepts. 
  • So, it generalises the specific features. Eg: BlackBox testing

Note: Throughout this post, you can refer this github link 

Concrete Class (Concrete Data Type CDT)

  • Concrete class is a derived class that has all the missing functionality.
  • The class which is instantiated with the keyword new is called concrete class 
  • It doesn't have any pure virtual function. 

  • Also, It doesn't hide the inner data structure.  
Eg: Point, LineSegment, Rectangle, Circle in link

Abstract Class (Abstract Data Type ADT)

  • Abstract class is basically defined to be a base class. 
  • A class which can't / shouldn't be instantiated with the keyword new is called abstract class. 
  • In C++, abstract class should have at least one pure virtual function. It also means it can be overriden in the sub class. (Eg: Shape class has contains as pure virtual function in link)

virtual void function() = 0; 

  • Abstract class does hide the inner details/data structure.
Eg: Shape class 

Pure Virtual

  • Pure virtual function is declared within a base class and defined in the derived class.
  • If there is no overriding function, compiler will not allow to have pure virtual. 
  • Pure virtual function is not implemented in the base class and must be implemented in derived class.  Click here for example (function contains)
  • After this pure virtual function, compiler guarantees that class with pure virtual can't be instantiated. 

                      Virtual + Pure = Abstract (Modelling of an object)

         If the class has just normal object (not pointer), pure and virtual will not work or runtime polymorphism will not work. 

Overriding 

Same function name and signature in derived class, overrides base class. Virtual applies to overriding.


 #include <iostream>
using namespace std;

class Army                         // Class declaration
{                 
public:

    virtual void warFighting() = 0;
    virtual void treatingPatients() {cout << __func__ << endl;} ;

};

class ArmyHospital : public Army {
public:
    string getName() {
        cout << " ArmyHospital  " << __func__ << endl;
        return "Shirley";
    }
    void treatingPatients() override {
        cout << " ArmyHospital  " << __func__ << endl;
    }
};

class ArmySchool : public ArmyHospital {

public:

    string getName() {
        cout << "ArmySchool  " << __func__ << endl;
        return "Rose";
    }

    void warFighting() override {
        cout << "ArmySchool  " << __func__ << endl;
    }
    
   
};

int main() 
{

    
    ArmyHospital *hos = new ArmySchool();
    hos->getName();            //ArmySchool
    cout << "\n";
    //ArmyHospital hello = *hos;
    //hello.warFighting();       // Army
    cout << "\n";
    Army *off = hos;
    off->warFighting();       // ArmySchool 
    off->treatingPatients();  // Army Hospital
    cout << "\n" ;
     
    // error: invalid conversion from ‘Army*’ to ‘ArmySchool*’ [-fpermissive] 
    //ArmySchool *sch = off;
    ArmySchool *sch = dynamic_cast<ArmySchool *>(off);
    sch->getName();           // ArmySchool
    return 0;
}


Output:

 ArmyHospital  getName


ArmySchool  warFighting

 ArmyHospital  treatingPatients


ArmySchool  getName

Up Casting 

Derived *dp =... 

Base *bp = static_cast <Base*> (dp); 

Use static cast or done implicitly.

Up-casting is implicit in C++, and is used a lot when you deal with virtual dispatching. In other words, you have a pointer to Base from which you can access the common interface of a whole hierarchy of classes, and the selection can be done at runtime. This assumes that your interface functions are marked virtual.

It is extremely useful in these situations in which the dispatch is done at runtime. Simply said, upcasting allows one to treat a derived class as a base class (via its common interface).


Down Casting 

Base *bp =... 

Derived *dp = static_cast <Derived *> (bp); 

Undefined behavior if bp doesn't point to Derived. 

Down-casting is less useful, and IMO should be avoided whenever one can. In general is a sign of bad design, as one rarely needs to convert a Base object to a derived one. It can be done (and the result checked) via dynamic_cast, like

Derived *dp = dynamic_cast <Derived *> (bp); 

Use dynamic cast for run-time checking

Comments