Saturday, August 6, 2011

The Virtues and Sins of Pure Virtual Implementations

                                             Beyond the Basics: the Deeper Dig into Design

Keywords: C++ Template Exception Data-Abstraction Pure-Virtual RAII Balking

virtual void implementMe() = 0;

A pure virtual method implemented in the Abstract class?
Can I do that?

Thats what I asked myself when I first discovered they in fact can be implemented in the Abstract Base class (thank you Scott Meyers).

Its not intuitive because the I-class MUST re-implement it anyway!
So why would I do that?

This blog will study its possible usage noting the Good, the Bad and the Ugly of implementing Abstract Class Pure Virtual Methods.

Since the original question of whether the Abstract Class Pure Virtual method can be implemented has been answered.
Lets dig a little deeper by asking a new question:

Isnt it bad practice to call a pure virtual from a constructor? Why ever do that?


The following is a design dig to answer this question:

Construction Feature Specific Initialization:

A study of construction through pure virtual methods and the associated design patterns.
A pattern of tying together specific base class initialization that I-classes will be dependent on.


Of course it doesnt need to be pure virtual to do this kind of initialization, just a virtual method can do that, a private initialization method or the constructor itself.

But a reason could be a pattern of tying together particular base class initialization that I-classes will be dependent on - a Construction Feature Specific Initialization

In fact we can look to several different well known design patterns to see a purpose to this...lower level design pattern, but specific to C++.

In general this means during Base class construction, the pure virtual initializes some resource that the I-class re-implementation will either complete, or use in its feature specific processing.

Naturally this implies a specialization of RAII as part of the design
pattern. In fact, you will see multiple patterns used to demonstrate
Construction Feature Specific Initialization:
RAII, Guarded suspension, AbstractFactory, Chain of responsibility, Singleton


1. Chain of Responsibility and Iterator patterns:

This is an often used technique, a sort of "Responder Design pattern". 
The chain could be an array, hash table, et al.
Responders often act on service request messages in client/server architectures.



int
initResponders()
{
    // create responders, registration is implicit via pure virtual base class implementation
    // they will placed in the responders list
    //
    Responder  jinx = new JinxResponder();
    Responder janky = new JankyResponder();
...

int
msgArrived(int msgNum, ... msg)
(
    Responder * responder = Responder::findResponder(msgNum);
    if (responder)
        return responder->respond(msg);

    return -1;
}

class Responder
{
public:
    static Responder * findResponder(int respondMask);

    virtual int respond(const char * str) = 0;
protected:
    Responder() { handles(-1); }
    virtual ~Responder();

    virtual Responder * handles(int respondMask) = 0;
private:
    static list<Responder *> _responderList;
};

list<Responder *> Responder::_responderList;

// pure virtual base class implementation
//
Responder * Responder::
handles(int respondMask)   // THIS is where the implicit registration happens
{
    _responderList.push_back(this);
    return 0;
}
Responder::
~Responder()
{
    // remove myself from the _responderList
    _responderList.remove(this);
}

// this is where the Iteration happens
//
Responder * Responder::
findResponder(int respondMask, const char * msg)
{
    for (list<Responder *>::iterator iter = _responderList.begin(); iter != _responderList.end(); ++iter)
    {
        Responder * resp =  (*iter)->handles(respondMask);
        if (resp)
            return resp;
    }
    return 0;
}

class JinxResponder : public Responder
{
public:
    static const int JINXVAL;
    JinxResponder() : Responder() {}
    int respond(const char * str) { cout << "Jinx responds to " << str << endl; }

protected:
    // NOTE: cannot use Prototype pattern here because of the implicit registration 
    Responder * handles(int respondMask) { return respondMask == JINXVAL ? this : 0; }
};
const int JinxResponder::JINXVAL = 99;


2. Balking with RAII: 

This demonstrates RAII and Balking using pthread Mutex Lock and Working safely when condition is ripe.
In this example the Base class checks precondition and acquires lock - else throws exception;
The I-class implements the required work safely.

// globals used in this example
//
#include <iostream>
#include <vector>
#include <list>

using namespace std;

bool KeepWorking = true; // flag for worker thread life cycle

bool DB_ConditionMet   = false;  // protected variable to determine if DB work should be done
bool SOCK_ConditionMet = false;  // protected variable to determine if Socket/msg work should be done

struct DBrecord;
struct MSGdata;
list<DBrecord *> DBworkQueue;
vector<MSGdata *> MsgWorkQueue;


int
main(int argc, char ** argv)
{
LockLifeCycle condlock;  // RAII for mutex construction and destruction

// create thread to perform some sort of work dependent on the condition vars XX_ConditionMet
//
pthread_t threadID;
        int ret = pthread_create(&threadID, 0, workerThread, &condlock);
if (ret != 0)
{
cout << "pthread_create failed: returned: " << ret << endl;
return ret;
}

// in a loop, do work and mark conditions as ready when required
//
while (KeepWorking)
{
.... doing some sort of processing ...
if (Conditions_got_set)
{
JustLock lock(&condlock);  // use RAII lock class
DB_ConditionMet   = true;
SOCK_ConditionMet = true;
}

int jret = pthread_join(threadID, 0);

return 0;
}


Now lets look at real implementations of the classes used above:


// controls creation and destruction of a mutex via RAII design
//
class LockLifeCycle
{
public:
LockLifeCycle()
{
}

~LockLifeCycle()
{
pthread_mutex_destroy(&_mutex);
}

pthread_mutex_t * mutex() { return &_mutex; }

private:
static pthread_mutex_t  _mutex;
};

pthread_mutex_t LockLifeCycle::_mutex = PTHREAD_MUTEX_INITIALIZER;

// Just locks and unlocks the mutex via RAII design
//
class JustLock
{
public:
JustLock(LockLifeCycle * llcp) : _llcp(llcp)
int rc = pthread_mutex_lock(_llcp->mutex());
if (rc != 0)
throw "JustLock: mutex failed";
}

~JustLock() { pthread_mutex_unlock(_llcp->mutex()); }

private:
LockLifeCycle * _llcp;
};

class LockAndWork
{
protected:
LockAndWork(LockLifeCycle * llcp, bool & conditionMet) : _llcp(llcp) { safeWork(conditionMet); }
virtual ~LockAndWork() { 
cout << "~LockAndWork: unlock mutex" << endl;
pthread_mutex_unlock(_llcp->mutex());
}

virtual void safeWork(bool & conditionMet) = 0;

LockLifeCycle * _llcp;
};

// check the cond var - throw exception if not ready
// We dont want to use RAII for the lock here since we want
// to keep the lock for safe work
//
void LockAndWork::
safeWork(bool & conditionMet)
{
int rc = pthread_mutex_lock(_llcp->mutex());
if (rc != 0)
throw "mutex failed";

// DANGER! if we throw an exception that means construction didnt complete therefore
// the destructor will NOT run
//
if (conditionMet == false)  // condition not ready for processing yet
{
pthread_mutex_unlock(_llcp->mutex()); // NOTE: destructor wont get run so release lock here
throw "condition not ready";
}

// HAVE: obtained lock so safe work may be performed with this object
}

// Pretend to do DB work of some sort
//
struct DBrecord { int _field; };
class DBwork : public LockAndWork
{
public:
DBwork(LockLifeCycle * llcp, bool & conditionMet, 

list<DBrecord *> & dbList) : 
   LockAndWork(llcp, conditionMet), _list(dbList) {}
void safeWork(bool & conditionMet)
{
cout << "DB work about to begin..." << endl;
conditionMet = false;
cout << "DB work completed" << endl;
}

private:
list<DBrecord *> & _list;
};

// Pretend to do socket work of some sort
//
struct MSGdata { int _field; };
class SOCKwork : public LockAndWork
{
public:
SOCKwork(LockLifeCycle * llcp, bool & conditionMet, vector<MSGdata *> & msgArray) : 
   LockAndWork(llcp, conditionMet), _array(msgArray) {}
void safeWork(bool & conditionMet)
{
cout << "Socket work about to begin..." << endl;
conditionMet = false;
cout << "Socket work completed" << endl;
}

private:
vector<MSGdata *> _array;
};