Solution

I l @ ve RuBoard

graphics/bulb.gif

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 BaseA s. 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.

graphics/04fig02.gif

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


More Exceptional C++
More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions
ISBN: 020170434X
EAN: 2147483647
Year: 2001
Pages: 118
Authors: Herb Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net