Sunday, October 25, 2009

Solution




I l@ve RuBoard









Solution



The purpose of this Item is to show you a minor pitfall that can come up with MI, and how to handle it effectively. Say you're using two vendors' libraries in the same project. Vendor A provides the following base class BaseA.



class BaseA
{
public:
virtual int ReadBuf( const char* );
// ...
};

The idea is that you're supposed to inherit from BaseA, probably overriding some virtual functions, because other parts of vendor A's library are written to expect objects they can use polymorphically as BaseAs. This is a common and normal practice, especially for extensible application frameworks, and there's nothing wrong with it.


Nothing, that is, until you start to use Vendor B's library and discover, to your uneasy amazement:



class BaseB
{
public:
virtual int ReadBuf( const char* );
// ...
};

"Well, that's rather a coincidence," you may think. Not only does vendor B, too, have a base class that you're expected to inherit from, but it happens to have a virtual function with exactly the same signature as one of the virtuals in BaseA. But BaseB's is supposed to do something completely different. And that's the key point.


Both BaseA and BaseB are clearly intended to be used as base classes but they are otherwise unrelated. Their ReadBuf() functions are intended to do different things, and the classes come from different library vendors.


Demonstrate how to write a class Derived, publicly derived from both BaseA and BaseB, which overrides both ReadBuf()s independently to do different things.


The problem becomes clear when you have to write a class that inherits from both BaseA and BaseB, perhaps because you need an object that can be used polymorphically by functions in both vendors' libraries. Here's a naïve attempt at such a function:



// Example 26-1: Attempt #1, doesn't work
//
class Derived : public BaseA, public BaseB
{
// ...

int ReadBuf( const char* );
// overrides both BaseA::ReadBuf()
// and BaseB::ReadBuf()
};

Here Derived::ReadBuf() overrides both BaseA::ReadBuf() and BaseB::Read Buf(). To see why that isn't good enough given our criteria, consider the following code:



// Example 26-1(a): Counterexample,
// why attempt #1 doesn't work
//
Derived d;
BaseA* pba = d;
BaseB* pbb = d;

pba->ReadBuf( "sample buffer" );
// calls Derived::ReadBuf

pbb->ReadBuf( "sample buffer" );
// calls Derived::ReadBuf

Do you see the problem? ReadBuf() is virtual in both interfaces, and it operates polymorphically just as we expect. But the same function, Derived::ReadBuf(), is invoked regardless of which interface is used. Yet BaseA::ReadBuf() and BaseB::ReadBuf() have different semantics and are supposed to do different things, not the same thing. Further, Derived::ReadBuf() has no way to tell whether it's being called through the BaseA interface or the BaseB interface (if either), so we can't put an "if" inside Derived::ReadBuf() to make it do something different depending on how it's called. That's lousy, but we're stuck with it.


"Oh, come on," you may be thinking. "This is an awfully contrived example, isn't it?" Actually, it's not. For example, John Kdllin of Microsoft reports that creating a class derived from both the IOleObject and IConnectionPoint COM interfaces (think of these as abstract base classes composed entirely of public virtual functions) becomes problematic, because (a) both interfaces have a member function declared as virtual HRESULT Unadvise(unsigned long); and (b) typically you have to override each Unadvise() to do different things.


Stop a moment and think about this example. How would you solve this problem? Is there any way we can override the two inherited ReadBuf functions separately so that we can perform different actions in each one, with the right actions getting performed, depending on whether outside code calls through the BaseA or BaseB interface? In short, how can we separate these twins?


How to Separate Siamese Twins


Fortunately, there is a fairly clean solution. The key to the problem is that the two overridable functions have exactly the same name and signature. The key to the solution, therefore, must lie in changing at least one function's signature, and the easiest part of the signature to change is the name.


How do you change a function's name? Through inheritance, of course! What's needed is an intermediate class that derives from the base class, declares a new virtual function, and overrides the inherited version to call the new function. The inheritance hierarchy looks like the one shown in Figure 5.


Figure 5. Using intermediate classes to rename inherited virtual functions.



The code looks like the following:



// Example 26-2: Attempt #2, correct
//
class BaseA2 : public BaseA
{
public:
virtual int BaseAReadBuf( const char* p ) = 0;
private:
int ReadBuf( const char* p ) // override inherited
{
return BaseAReadBuf( p ); // to call new func
}
};

class BaseB2 : public BaseB
{
public:
virtual int BaseBReadBuf( const char* p ) = 0;
private:
int ReadBuf( const char* p ) // override inherited
{
return BaseBReadBuf( p ); // to call new func
}
};

class Derived : public BaseA2, public BaseB2
{
/* ... */

public: // or "private:", depending whether other
// code should be able to call these directly

int BaseAReadBuf( const char* );
// overrides BaseA::ReadBuf indirectly
// via BaseA2::BaseAReadBuf

int BaseBReadBuf( const char* );
// overrides BaseB::ReadBuf indirectly
// via BaseB2::BaseBReadBuf
};

BaseA2 and BaseB2 may also need to duplicate constructors of BaseA and BaseB so that Derived can invoke them. But that's it. (Often a simpler way than duplicating the constructors in code is to have BaseA2 and BaseB2 derive virtually so that Derived has direct access to the base constructors.) BaseA2 and BaseB2 are abstract classes, so they don't need to duplicate any other BaseA or BaseB functions or operators, such as assignment operators.


Now everything works as it should.



// Example 26-2(a): Why attempt #2 works
//
Derived d;
BaseA* pba = d;
BaseB* pbb = d;

pba->ReadBuf( "sample buffer" );
// calls Derived::BaseAReadBuf

pbb->ReadBuf( "sample buffer" );
// calls Derived::BaseBReadBuf

Further-derived classes need only to know that they must not further override ReadBuf itself. If they do, it will disable the renaming stubs that we installed in the intermediate classes.









    I l@ve RuBoard



    No comments: