Click here to Skip to main content
15,867,704 members
Articles / Programming Languages / C++

Interfacing without Inheritence in C++

Rate me:
Please Sign up or sign in to vote.
4.71/5 (34 votes)
11 Oct 2010CPOL11 min read 68.5K   241   29   36
An alternate way to implement interface through aggregation rather than the usual way we know as inheritence

Preface

Have a problem with extensive use of pointers? Totally abhor macros? Do you find the following way of interface declaration totally despicable?

C++
ABSTRACT_CLASS(IDynamicCtrl)
    ABSTRACT_METHOD(void, Draw, CDC*); 
    ABSTRACT_METHOD(BOOL, Edit);
    ABSTRACT_METHOD(BOOL, IsEditing);
    ABSTRACT_METHOD(void, EndEditing);
    ABSTRACT_METHOD(void, SetCtrlType, int); 
    ABSTRACT_METHOD(void, SetDisplayInfo, LPCTSTR pszInfo);
    ABSTRACT_METHOD(BOOL, AddData, LPARAM);
    ABSTRACT_METHOD(void, ResetData);
    ABSTRACT_METHOD(void, UpdateGUI); 
END_ABSTRACT_CLASS()

Then don't bother :). This article is not for you. Otherwise, please carry on..

Introduction

Changing a comment in the header makes you recompile an entire project, containing nearly 1 million lines (maybe a little exaggeration) and taking almost half of your working hours - if you ever had to put up with this situation, then come onboard, let's share some ideas on how to get rid of such a disturbing effect on the mind.

Here recompilation is an indication of how tightly or loosely your classes are coupled; It gives some insight about how flexible your design is and how easily you can accommodate changes in your modules - such an addition of an extra class or deletion of an existing class.

As the GOF has described in design patterns, the more you make your classes loosely coupled, the easier it is for you to accommodate changes (one example of loose coupling can be, instead of declaring a member variable directly in the header, keep a pointer and instantiate in the CPP).

Whether you do without loose coupling or tight coupling, one thing you cannot live without in the modern world is interface, or in other words implementation of interface. We all know how we do it in C++, we'll have an abstract class containing pure virtual methods and we implement those methods in the derived classes. This means, even a single line change in the header of an abstract class (interface), even if it is a comment ends up compiling a lot of classes derived from it. Although I am talking about interfaces, sometimes people who are programming in C++ make their life a bit easier by declaring some member variables in the abstract classes which are supposed to be interfaces. I cannot comment whether it's good or bad, but sometimes due to short deadlines, we have to do it (giving up to the dreaded multiple inheritance - the horror in modern programming paradigm). So, in these cases even if a member variable name is changed, or new members are added, you still end up compiling a lot of classes which inherited from these abstract classes.

Those of us, who are not running a super computer, need a solution for this in big projects. Let me again re-iterate: recompilation problem solving is not the main objective here, the main objective is to achieve loose coupling and very light level of dependencies among classes.

So How Do We Plan to Meet Our Objective?

First of all, let me tell you - even I, myself, wouldn't be using these techniques I mention here, for now; at least not in my professional projects yet. Maybe, after 100 years, when(?) this article gets a noble prize or something similar for its concept, then some of you people might start using the techniques I describe here (given C++ remains the ultimate programming language even then).

As the gurus of the design world always say, aggregation is a better concept than inheritance -but what about interface implementation which is also done through multiple inheritance in C++. This is where I came up with this idea of applying the concept of aggregation in interface implementation.

Here the main concept is the user will have an interface aggregated inside the aggregator, but invoking of the interface method will end up calling the method of the aggregator class.

Speaking more visually, instead of declaring something like:

C++
class ConcreteClassA : public AbstractClassA 
{
public: 
    virtual void CallFunction1();
};

We'd declare:

C++
class ConcreateClassA
{
public:    
    AbstractClassA* m_pAbstractClassAPointer;
    void CallFunction1();
};

You can imagine what AbstractClassA looks like, right?

C++
class AbstractClassA
{ 
public: 
    virtual void CallFunction1() = 0 ; // This is a pure virtual method
};

And somehow through a pointer of the AbstractClassA, we'd invoke the ConcreteClassA method CallClassFunction1();
Quite amazing, don't you think?. We will call the overridden virtual function of the interface AbstractClassA by a pointer of AbstractClassA type only, but that too, without inheritance. So, how?

Yes!! You guessed right. Through function pointers; to be more precise: method pointers. Now, AbstractClassA which used to look like this:

C++
class AbstractClassA
{ 
public: 
    virtual void CallFunction1() = 0 ; // This is a pure virtual method
};

will now look like what you see below:
(To quote a famous lyricist Bob Dylan : "It used to be like that, now it goes like this..")

C++
class AbstractClassA
{ 
public: 
    //typedef of a method pointer; the method pointer represents a pointer type,
    //to the CallFunction1
    typedef  void (AbstractClassA::*__CallFunction1)();
    //declaration of the actual method pointer
        __CallFunction1 CallFunction1; 
    //Note: CallClassFunction1 is now a method pointer and not a pure virtual method
    //inside AbstractClassA    
};

Ok, now we managed to have the method pointer inside AbstractClassA but how to invoke the actual method of ConcreteClassA through it? And also, you need an object to invoke any method pointer. And this object cannot be any object, this has to be the instance of ConcreteClassA which holds AbstractClassA inside it.

So, the solution I came up with was - I would make sure my AbstractClassA is properly initialized, i.e., its function pointer has a valid value and it contains the pointer to the Concrete class in void pointer form. Unethical? This is nothing compared to instantiating the abstract AbstractClassA in the constructor of ConcreteClassA. But we are here for a noble cause, isn't it? And that is to implement interface without inheritance; so we have to play dirty a little. Just tell your fellow programmers not to directly instantiate AbstractClassA anywhere else, ok? It will only be instantiated in the constructor of those concrete classes who treat it as their interface to the outside world.

Going forward with this inventive(!) thinking, this is how we'd create and initialize an instance of AbstractClassA in the constructor of ConcreteClassA:

C++
ConcreteClassA::ConcreteClassA()
{
    AbstractClassA* q = new AbstractClassA();
    if(q)
    {
    q->SetObj(this); 
    q->CallFunction1 = (AbstractClassA::__CallFunction1)(&ConcreteClassA::CallFunction1);
    m_pAbstractClassAPointer = q; 
    }

    ....
}

Here the main initialization involves the assigning of the function, i.e., concrete class's method to the abstract class's method pointer and the setting of a reference of the concrete class in void pointer form inside the abstract class.

To make provision for the void pointer which is actually the address of the concrete class, we have to add methods like SetObj, GetObj and a relevant member variable in AbstractClassA making the complete picture of our abstract class looking similar to what follows:

C++
class AbstractClassA
{ 
public: 
    //typedef of a method pointer; the method pointer represents a pointer type,
    //to the CallFunction1
    typedef  void (AbstractClassA::*__CallFunction1)();
    //declaration of the actual method pointer
        __CallFunction1 CallFunction1; 
    //Note: CallClassFunction1 is now a method pointer and not a pure virtual method
    //inside AbstractClassA

    
    //method to set the pointer to concrete class
    void SetObj(void* pObj){m_pObj = pObj;}
    //method to get in instance of the concrete class forcefully 
    //typecasted to abstract class
    AbstractClassA* GetObj(){ return m_pObj; }

protected:
    // pointer to the concrete class
    void* m_pObj;
};

And this is how we'd invoke the actual method CallFunction1 of ConcreteClassA if we have a pointer to AbstractClassA named pAbstractClassA:

C++
( (pAbstractClassA->GetObj())->*AbstractClassA->CallFunction1 )();

Notice the clever typecasting of the void pointer to AbstractClassA* through GetObj(). This allows us to pass the this pointer of ConcreteClassA to the CallFunction1 method through AbstractClassA's method pointer. It's not a problem that we have typecasted the concrete class pointer to a completely different data type named AbstractClassA; It is due to the fact that a method pointer only requires a valid address of a proper object to be passed (as this pointer) when the method gets invoked irrespective of its type. We are casting the void pointer to AbstractClassA* only to get access to the function pointer inside it.

Since the code to invoke the method looks a bit clumsy, I came up with some useful macros for it. Also, to declare an abstract class which will always have some common methods such as GetObj(), SetObj - I came up with some more macros.

How It All Looks With the Advent of Macros

Before I start giving an example (with macros) of an interface implemented through aggregation techniques, let me talk about my interface and the implementor of the interface a little.

The name of my interface here is IDynamicCtrl. The implementor class is CDynamicCtrl which looks and acts like an edit box by default, but it's outlook and behaviour changes to a combo box when its interface method SetCtrlType is invoked. In short, IDynamicCtrl is an interface of a GUI control which can change dynamically its type in runtime.

Following is the structure of my interface IDynamicCtrl with macros:

C++
ABSTRACT_CLASS(IDynamicCtrl)
    ABSTRACT_METHOD(void, Draw, CDC*); 
    ABSTRACT_METHOD(BOOL, Edit);
    ABSTRACT_METHOD(BOOL, IsEditing);
    ABSTRACT_METHOD(void, EndEditing);
    ABSTRACT_METHOD(void, SetCtrlType, int); 
    ABSTRACT_METHOD(void, SetDisplayInfo, LPCTSTR pszInfo);
    ABSTRACT_METHOD(BOOL, AddData, LPARAM);
    ABSTRACT_METHOD(void, ResetData);
    ABSTRACT_METHOD(void, UpdateGUI); 
END_ABSTRACT_CLASS()

I have taken all the intricacies of the interface declaration and its method pointer declarations inside the macros named ABSTRACT_CLASS, END_ABSTRACT_CLASS and ABSTRACT_METHOD.

So, to me it looks a little better and more readable with macros.

This is how the implementor class CDynamicCtrl aggregates the interface IDynamicCtrl:

C++
// CDynamicCtrl

class CDynamicCtrl : public CStatic
{ 
    DECLARE_DYNAMIC(CDynamicCtrl)

    ///////////// usage of aggregation macros/////////////////
    ABSTRACT_CLASS_AGGREGATION_SUPPORT(CDynamicCtrl) 
    DECLARE_ABSTRACT_CLASS(IDynamicCtrl)
    ///////////////////////////////////////////
public:

    CDynamicCtrl();
    virtual ~CDynamicCtrl(); 
protected:

    DECLARE_MESSAGE_MAP()
    afx_msg void OnChildKillFocus();

    ///////The interface implementing methods/////////

    void Draw(CDC* pDC);
    BOOL Edit();
    BOOL IsEditing();
    void EndEditing();
    void DrawCombo(CDC* pDC);
    BOOL EditCombo();
    // nType: 0 for edit, 1 for combo 
    void SetCtrlType(int nType);
    void SetDisplayInfo(LPCTSTR pszInfo);
    BOOL AddData(LPARAM lp);
    void ResetData();
    void UpdateGUI();
    //////////////////////////////////////////////////

    CStringArray m_arrStrings;
    BOOL m_bFocused;
public:

    afx_msg void OnPaint();
    afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
    afx_msg void OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnSetFocus(CWnd* pOldWnd);
    afx_msg void OnKillFocus(CWnd* pNewWnd);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
};

The macros required for defining the structure of an interface and its aggregation in concrete classes are declared in "abstract_class_decl.h" header of the uploaded source code.

Now, let's have a look at initialization of the interface class IDynamicCtrl and the mapping of concrete class CDynamicCtrl functions with the abstract class's function pointers (Since we are dealing with C++, I am using the terms interface class and abstract class interchangeably):

C++
IMPLEMENT_ABSTRACT_CLASS(CDynamicCtrl, IDynamicCtrl) 

CDynamicCtrl::CDynamicCtrl()
{
	BEGIN_ABSTRACT_METHOD_MAP(CDynamicCtrl, IDynamicCtrl)
		ABSTRACT_METHOD_MAP(Draw)
		ABSTRACT_METHOD_MAP(Edit)
		ABSTRACT_METHOD_MAP(IsEditing)
		ABSTRACT_METHOD_MAP(EndEditing)
		ABSTRACT_METHOD_MAP(SetCtrlType)
		ABSTRACT_METHOD_MAP(SetDisplayInfo)
		ABSTRACT_METHOD_MAP(AddData)
		ABSTRACT_METHOD_MAP(ResetData)
		ABSTRACT_METHOD_MAP(UpdateGUI)
	END_ABSTRACT_METHOD_MAP() 

	...
}

The macros related to the implementation (instantiation and method mapping) of the abstract class in concrete classes is declared in "abstract_class_impl.h" header file of the uploaded source code.

And finally and most importantly, let's have a look at the macro related to the invoking of a CDynamicCtrl method through IDynamicCtrl interface (see if it suits you, because that's the most readable version of invoking a concrete class method I could come up with):

C++
// m_pInfoDisplayer is a saved pointer of the IDynamicCtrl interface
// which was retrieved through CDynamicCtrll::QueryIDynamicCtrl method
IDynamicCtrl* pCtrl = m_pInfoDisplayer; 
if(nViewType == 1)
{
	if(pCtrl)
	{
		(CONCRETE(pCtrl)->SetCtrlType)(nViewType);
	}
}

If (CONCRETE(pCtrl)->SetCtrlType)(nViewType) does not suit you, you can use INVOKE(pCtrl, SetCtrlType, nViewType) macro. Both CONCRETE and INVOKE macro is defined in "abstract_class_impl.h" header file.

Background Behind the Aberration

The whole thing about implementing interface through aggregation rather than inheritance came to picture, because I work in a big project which has a lot of interface implementation through inheritance in several modules and sometimes writing comments on the header files ends up compiling the whole project. While thinking about the recompilation problem, the thought of removing one of the tightest form of coupling (bearing the name interfaces) came to mind. The article is an end result of my contemplative thinking upon inheritance and interfacing and whether it would be a nice thing to have in the C++ language itself if it really supported interfacing through aggregated classes.

Practically speaking, there were certain benefits I found when I ended up writing a sample application for this article.

Constructive Benefits from this Design

First and foremost, this concept reduces appreciably the possible ambiguity factor which might be introduced due to multiple inheritance. Like I mentioned before, working as C++ programmers under tight deadlines, you end up using the abstract class concept (which has variables) more than the interface concept (interface shouldn't have a single non static member variable). Over time, this increases ambiguity because of similar method and variable names in some of the parent classes. This technique here encourages the usage of composition of multiple objects rather than multiple inheritance and in return solves the problem related to ambiguity completely.

The other benefit is, you can change dynamically the run time behaviour of certain interface methods. For example, to give a combo look and combo behaviour to the CDynamicCtrl when the control type changes, I just remap the Draw and Edit method pointers of IDynamicCtrl to the DrawCombo and EditCombo methods of CDynamicCtrl, achieving greater flexibility and manageability in the entire process. If you'd have used inheritance instead, probably you'd end up creating another class to encapsulate different behaviours and use the correct class to display proper behaviour given the control type following the footsteps of strategy pattern; or in the worst case, you'd be using if else conditions increasing the complexity of the code - when you know very well, one of the reasons design patterns and modern techniques were introduced, were to minimize the usage of if else conditions and spaghetti codes.

The downside of this design is the way I implemented the macros, it's not possible to support polymorphic features such as function overloading as of now. However, writing the macros more cleverly and using an array of function pointers can solve this problem, but that is beyond the scope of this article.

The horror of this design, as some people may point out, is the use of void pointer, complete disregard for type safety, and breaking several ethics of OOP design; but all the hideous things mentioned above are hidden underneath the carpet of macros and the discretion of the user of macros is requested so that he may not have a peek at what's inside :).

However, like I pointed out before, if C++ supported something of this sort, the theory in practice could have been real elegant. We could ponder upon dynamic binding of an interface to a concrete class in runtime even. I know, most people are not wary of adding extra classes to encapsulate behaviour for runtime behaviour changing, but in my view if we could reduce some overheads in some cases, then why not?

During runtime, if I could write codes such as:

C++
ISortable& sortableData = _bind(CDataTable, ISortable) // _bind is a keyword of future :)
		        {   //Sort is an ISortable interface method, and 
			   //SortByID and SortByName are CDataTable methods
			   _connect(Sort, bSortByID ? SortByID : SortByName)
		        } 

And then invoke:

C++
Sort(sortableData) 

where The Sort function takes a reference of the ISortable interface - maybe this dynamic binding of an interface (ISortable) with a concrete class (CDataTable) could have been really useful in some cases.

A Decade Later (Updated on Dec 20, 2020)

With the cool C++17 features it is now possible to achieve the holy grail of decoupling, using std::any and lamdas.

The famous shape example of C++, where you inherit rect and circle from shape can now be modified to decouple shape from rect and circle thusly:

Decoupling

 

Acknowledgements

  • Kaisar Ul Haque: Who helped me in many ways to understand the complex things about method pointers, the this pointer and virtual tables through his blogs.

History

  • 6th October, 2010: Article uploaded
  • 20th December, 2020: Article updated 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Kotha Technologies
Bangladesh Bangladesh
If you are not in - you are out !
- Chapter 1

Comments and Discussions

 
GeneralRe: My vote of 1 Pin
Mukit, Ataul9-Oct-10 19:45
Mukit, Ataul9-Oct-10 19:45 
GeneralRe: My vote of 1 Pin
Tim Craig9-Oct-10 20:53
Tim Craig9-Oct-10 20:53 
GeneralMy vote of 1 Pin
Aescleal7-Oct-10 2:59
Aescleal7-Oct-10 2:59 
GeneralRe: My vote of 1 Pin
Mukit, Ataul8-Oct-10 21:19
Mukit, Ataul8-Oct-10 21:19 
GeneralRe: My vote of 1 Pin
Aescleal9-Oct-10 11:28
Aescleal9-Oct-10 11:28 
QuestionNice, but what about polymorphism? Pin
DBarzo6-Oct-10 22:25
DBarzo6-Oct-10 22:25 
AnswerRe: Nice, but what about polymorphism? [modified] Pin
Mukit, Ataul6-Oct-10 22:31
Mukit, Ataul6-Oct-10 22:31 
GeneralNeat Article Pin
Harrison H6-Oct-10 10:46
Harrison H6-Oct-10 10:46 
GeneralRe: Neat Article Pin
Mukit, Ataul6-Oct-10 16:08
Mukit, Ataul6-Oct-10 16:08 
GeneralRe: Neat Article Pin
Kaisar Haque11-Oct-10 2:11
Kaisar Haque11-Oct-10 2:11 
GeneralRe: Neat Article Pin
Mukit, Ataul11-Oct-10 6:02
Mukit, Ataul11-Oct-10 6:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.