Inheritance

Inheritance

One can extend a parent class with additional behavior. Then the class automatically contains the data members and methods of the original parent or base class. Here is the syntax:
class Base
{
    public:
        void fooMethod();
    protected:
        string mProtectedString;
    private:
        double mPrivateDouble;
}
To derive from this base class:
class Derived: public Base
{
    public: 
        void aNewMethod();
}
Additional classes can inherit from Base and they would be siblings of Derived. The Base class has no idea about the Derived class. A pointer or reference to Base could actually be pointing at a Derived. For example, the following code is valid:
Base* b = new Derived();
However, one cannot call methods in derived on b. Derived class has access to public and protected members of the base class. To prevent inheritance, one can mark a class as final. In order to override methods of the base class in a derived class, they must be marked virtual. It is a good rule of thumb to mark every method except the constructor virtual.
To override a method, add the keyword override in the .h file of the derived class at the end like so:
class Derived: public Base
{
    public: 
        virtual void fooMethod() override;
}
If you have a Base reference to a Derived object, the correct method is called.
Derived der;
Base& baseRef = der;
baseRef.fooMethod(); //calls the Derived version
However, the following calls the Base method:
Derived der;
Base b = der;
b.fooMethod(); //Calls the base version
The loss of the derived class's data members and member functions in the above example is known as slicing. If a method is not virtual, trying to override it will result in unexpected behavior as it just hides the base class method instead of overriding it.
virtual implementation
When a non-virtual method is encountered, the code to transfer control to the called method is hardcoded in what is known as static or early binding. For virtual methods, a vtable is used as follows:
class Base
{
    public:
        virtual void virtFunc1() {}
        virtual void virtFunc2() {}
        void regularFunc() {}
}

class Derived : public Base
{
    public: 
        virtual void virtFunc2() override {}
        void regularFunc() {}
}

Base base;
Derived der;


The reason the programmer has to specify virtual is the small performance penalty of the vtable. It is a good practice to make all destructors virtual. Non virtual destructors might result in cases when the derived class's destructor is not called. Example:
Base *p = new Derived;
delete p; //Base dtor is called, but not the derived destructor.
Mark a class as final to prevent inheriting from it.
virtual void foo() final;

Parent Constructors and Creation Order

  1. If the class has a base class, the default ctor of the base class is executed unless another ctor is invoked in the ctor-initializer of the derived class.
  2. Non-static data members are constructed in the order in which they were declared.
  3. The body of the constructor is executed.
Passing constructor arguments from the derived to the base class constructor is normal. However, because data members are initialized after the base class, they can't be passed to the base class.

Parent Destructors and Destruction Order

The order of destruction is as follows:
  1. The current class's destructor is called.
  2. Data members are destroyed in the reverse order of construction.
  3. Parent destructor is called.

Referring to parents from derived classes

To call the base class version of a member function use the scope resolution operator:
int Derived::foo()
{
    return Base::foo() + 2;
}
As shown above when upcasting to a base class, take a pointer or reference to avoid slicing.
Base& base = derived;
Downcasting should be avoided, and if really needed, a dynamic cast should be used.

Revisiting the CheckingAccount class

So far, the checking account class only handles one type of currency (dollars). We need to extend it to handle pounds as well. A first attempt might look similar to the following:
class CheckingAccount
{
    public:
        virtual void deposit(double amount);
        virtual void withdraw(double amount);
        virtual double getBalance();
    private:
        static double dollarsToPounds(double dollars);
        static double poundsToDollars(double pounds);
        double mAccountBalance;
        std::string mAccountNumber;
};

Polymorphic CheckingAccount

DollarsCheckingAccount and PoundsCheckingAccount are sibling derived classes that derive from CheckingAccount base class. The properties of this scenario are:
  • Both derived classes support the same interface as the base class
  • Code can use CheckingAccount objects without knowing whether it is a DollarsCheckingAccount or a PoundsCheckingAccount.
  • Through virtual methods, the appropriate method is called.
  • The Bank class can contain a collection of multi-typed objects (using CheckingAccount objects).

CheckingAccount base class

All checking accounts need to be able to deposit and withdraw, so the base class has these methods.
class CheckingAccount
{
    public:
        virtual void deposit(double amount);
        virtual void withdraw(double amount);
        virtual double getBalance();
};
However, the base class has no way of implementing these methods because it does not know whether we are storing dollars or pounds. The solution is to use pure virtual methods and abstract base classes. For a pure virtual method, the method is followed by =0. Here is the CheckingAccount class now:
class CheckingAccount
{
    public:
        virtual ~CheckingAccount() = default;
        virtual void deposit(double amount) = 0;
        virtual void withdraw(double amount) = 0;
        virtual double getBalance() = 0;
        virtual std::string getAccountNumber();
    private:
        double mAccountBalance;
        std::string mAccountNumber;

};

std::string CheckingAccount::getAccountNumber()
{
    return mAccountNumber;
}
The abstract base class cannot be instantiated, however, the derived classes can be. The following works:
std::unique_ptr<DollarsCheckingAccount> = make_unique<DollarsCheckingAccount>(1000.00,"1234");
DollarsCheckingAccount can be defined as follows:
class DollarsCheckingAccount : CheckingAccount
{ 
    public:
        virtual void deposit(double amount) override;
        virtual void withdraw(double amount) override;
        virtual double getBalance() override;
};

//mAccountBalance and the values passed in are dollars.

void DollarsCheckingAccount::deposit(double amount)
{ 
    mAccountBalance += amount;
}

void DollarsCheckingAccount::withdraw(double amount)
{
    if((mAccountBalance - amount) > 0)
    {
        mAccountBalance -= amount;
    }
}
double DollarsCheckingAccount::getBalance()
{
    return mAccountBalance;
} 
PoundsCheckingAccount can be defined as follows:
class PoundsCheckingAccount : CheckingAccount
{
    public:
        virtual void deposit(double amount) override;
        virtual void withdraw(double amount) override;
        virtual double getBalance() override;
    private:
        static double dollarsToPounds(double dollars);
        static double poundsToDollars (double pounds);
}

void PoundsCheckingAccount::deposit(double amount)
{
    double poundsValue = dollarsToPounds(amount);
    mAccountBalance += poundsValue;
}

void PoundsCheckingAccount::withdraw(double amount)
{
    double poundsValue = dollarsToPounds(amount);
    if((mAccountBalance - amount) > 0)
    {
        mAccountBalance -= amount;
    }
}

void PoundsCheckingAccount::getBalance()
{
    double dollarsValue = poundsToDollars(mAccountBalance);
    return dollarsValue;
}

Using polymorphism

vector of accounts can be made:
vector<unique_ptr<CheckingAccount>> accountsVector;
Any of the methods declared in the base class can be applied and the correct method from DollarsCheckingAccount or PoundsCheckingAccount will be called:
accountsVector.push_back(make_unique<DollarsCheckingAccount>(1000.00,"1234"));
accountsVector.push_back(make_unique<PoundsCheckingAccount>(1000.00,"5678"));
accountsVector.push_back(make_unique<DollarsCheckingAccount>(2000.00,"9101112"));

accountsVector[0]->deposit(1000.00);
accountsVector[1]->withdraw(500.00); 

Converting constructor

A converting constructor can convert between the two types of checking accounts:
class DollarsCheckingAccount: CheckingAccount
{
    public:
        DollarsCheckingAccount(double balance, string accountNumber);
        DollarsCheckingAccount(const PoundsCheckingAccount& poundsCheckingAccount); //converting constructor
}
The implementation of the converting constructor is as follows:
DollarsCheckingAccount::DollarsCheckingAccount(const PoundsCheckingAccount& poundsCheckingAccount)
{
    mAccountBalance = poundsCheckingAccount.getBalance(); //getBalance auto converts to dollars.
    mAccountNumber = poundsCheckingAccount.getAccountNumber(); 
}

Operator overloading

Operator overloading on the DollarsCheckingAccount might be as follows:
DollarsCheckingAccount operator+(const DollarsCheckingAccount& lhs,
                                const DollarsCheckingAccount& rhs)
{
    DollarsCheckingAccount newAct(0.0,"<unique_account_number>");
    newAct.deposit(lhs.getBalance() + rhs.getBalance());
    return newAct;
}
As long as the compiler can turn an object into a DollarsCheckingAccount the above operator will work. Given the converting constructor above, the following will work:
PoundsCheckingAccount pndsAct(1000.00,"1234");
pndsAct.deposit(500.00);
DollarsCheckingAccount dlrsAct = pndsAct + pndsAct;

Copy Constructors and Assignment operators in derived classes

If the derived class omits the copy constructor and the = operator, a default copy constructor and assignment operator will be provided for the derived class's data members and the base class copy constructor and assignment operator will be used for the base class's data members. If you do specify a copy constructor and assingment operator in derived class, you need to call the base class versions:
class Base
{
    public:
        virtual ~Base() = default;
        Base() = default;
        Base(const Base& src);
};

Base::Base(const Base& src)
{
}

class Derived : public Base
{
    public:
        Derived() = default;
        Derived(const Derived& src);
};

Derived::Derived(const Derived& src) : Base(src)
{
}

Derived& Derived::operator=(const Derived& rhs)
{
    if (&rhs == this) {
        return *this;
    } 
    Base::operator=(rhs); // Calls parent's operator=.
    // Do necessary assignments for derived class.
    return *this;
} 

Comments

Popular posts from this blog

QTreeView and QTableView dynamic changes

C++ strings and string_view