Page 1 of 1

Implementation Inheritance Rate Topic: -----

#1 Anarion  Icon User is offline

  • The Persian Coder
  • member icon

Reputation: 316
  • View blog
  • Posts: 1,530
  • Joined: 16-May 09

Posted 12 December 2014 - 08:07 AM

Introduction


Inheritance can be categorized in different ways. Private, Public or Protected inheritance is not of interest here. Rather, the interest is in the reason behind inheritance:
  • To setup a specific interface in related classes. Here, the emphasis is on the unity of interface.
  • To use the implementation of another class and avoid code duplication as much as possible. Although the interface tends to be similar here as well, but the emphasis is more on borrowing another class' implementation rather than just the interface.

The first category is usually called interface inheritance while the second category is usually called implementation inheritance. In this tutorial, the focus is solely on implementation inheritance.

Main Goal


To reduce code duplication.
Sometimes, it is easy to just copy/paste little parts of code here and there as quick fixes for bad design or imperfect use of inheritance. Doing so only brings difficulties in the long run.
A couple weeks ago, I faced such situation. I was implementing a specialized matrix class, only to realize that I should design two versions of this matrix: one that stores data row-wise and the other column-wise. These two classes are very similar in implementation and exactly the same in interface, only a few member functions needed to be changed to present new element retrieval algorithms.
I decided to implement one of them and derive the other from this one. Although, later on, I converted the whole design to utilize templates. We will get to it later.

C++11 Features That Will Be Used


  • Inheriting Constructors
  • (Optional Delegating Constructors
  • (Optional) Final
  • (Optional) Move Semantics

Note: optional means that a certain feature was used in the code while not necessary to achieve the main goal of this tutorial. Using these optional features could have been avoided; but I believe they can help to encourage readers to begin learning/using them.

Implementation Inheritance


Implementation inheritance, in simple words, means:
To design a set of related classes in a way that the base class implements much of the functionality. Therefore, the derived classes are just (slightly) modified versions of the base class.

Lets say there is a class called impl1 (comes from implementation 1):
class impl1
{
public:

   impl1(); //default constructor
   impl1(int); //ordinary constructor
   impl1(impl1 const&); //copy constructor
   impl1(impl1&&); //move constructor

   impl1& operator=(impl1 const&); //copy assignment
   impl1& operator=(impl1&&); //move assignment

   int get() const; //a getter method
   void set(int const&); //a setter method
   virtual void f(); //just some trivial function, virtual because I am interested in replacing it in a derived class later on.

protected:
   int _a;
};

To avoid unnecessary clutter and make readers focus on the general state of the class, I have omitted the function definitions from the above code. It may still be helpful to see what the above functions do and what they output. You can open up the spoiler to see the definitions.
Spoiler

Now, assume that there is need to provide a modified version of this class (only f() needs to be changed, the internal data members are identical). Lets call this second class impl2:
class impl2 final: public impl1
{
public:
   using impl1::impl1; //inherit the constructors as well! C++11 feature
   void f();
};

impl2 replaces impl1's version of f(). This is the only part of the implementation that must be modified, the rest is the same for the two classes. final was used to indicate that the design has to be closed and other classes are not meant to derive from impl2.
Spoiler

Now, impl2 should have all of impl1's constructors, member functions and internal data members. Also, it provides a modified version of f(). Lets test these things:
#include <iostream>
#include "classes.h"

using namespace std;

int main()
{
   cout<<"Testing impl1 object:\n";
   impl1 b{2};
   b.f();
   cout<<"\nTesting impl2 object:\n";
   impl2 d{5};
   d.f();

   cout<<"\nTesting assignment:\n";
   b = d;
   d = b;

   return 0;
}

Of course, declaring and defining these two classes are necessary before executing the above code, and I assume you already know how to take care of it. I put the declarations and definitions in the file classes.h.

Trying to compile the above code does not go well, unfortunately. Here are the errors:

Quote

|=== Build: Debug in Implementation Inheritance (compiler: GNU GCC Compiler) ===
\main.cpp|In function 'int main()':
\main.cpp 17|error: no match for 'operator=' (operand types are 'impl2' and 'impl1')
\main.cpp 17|note: candidates are:
\classes.h 26|note: impl2& impl2::operator=(const impl2&)
\classes.h 26|note: no known conversion for argument 1 from 'impl1' to 'const impl2&'
\classes.h 26|note: impl2& impl2::operator=(impl2&&)
\classes.h 26|note: no known conversion for argument 1 from 'impl1' to 'impl2&&'
|=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

This might look weird at first. b = d (assigning derived to base) compiles just fine whereas d = b (assigning base to derived) does not compile. It looks like that the inherited assignment operators cannot be seen in the impl2 class.

Do not forget than the compiler generates a default assignment operator (and a default move assignment operator as of C++11) if you don't provide any of them. The fact that impl2 is derived from impl1 does not change this behavior: the compiler still provides default assignment operators and by doing this, it hides the inherited assignment operators. This basically means that a call such as d = b is not valid because the compiler has added impl2& operator=(const impl2&) and impl2& operator=(impl2&&) and by doing so, the inherited impl1& operator=(const impl1&) and impl1& operator=(impl1&&) are hidden.

But how should such problems be solved?

There is a solution to this: make the inherited assignment operators visible in impl2's scope:
class impl2 final: public impl1
{
public:
   using impl1::impl1;
   using impl1::operator=;
   void f();
};

With the help of another using statement, impl1's assignment operators can be brought into impl2's scope. By doing this, the compiler doesn't hide the inherited assignment operators anymore and both b = d and d = b work well.
Here's the output of the program:

Quote

Testing impl1 object:
impl1::impl1(int) was called -> initialize _a to 2
impl1::f() was called

Testing impl2 object:
impl1::impl1(int) was called -> initialize _a to 5
impl2::f() was called

Testing assignment:
impl1::operator=(&) was called
impl1::operator=(&) was called


An Alternative Approach


Templates can be used to eliminate the need for derivation in this context. The alternative template approach is going to be discussed in the second part of this tutorial.

This post has been edited by Anarion: 12 December 2014 - 01:43 PM


Is This A Good Question/Topic? 1
  • +

Page 1 of 1