Adaptations

When there are no other solutions on the horizon, intermediate adaptors can be a good way to get the job done.


November 01, 2004
URL:http://drdobbs.com/adaptations/184401879

November, 2004: Adaptations

Herb Sutter (http://www.gotw.ca/) is a leading authority and trainer on C++ software development. He chairs the ISO C++ Standards committee and is a Visual C++ architect for Microsoft, where he is responsible for leading the design of C++ language extensions for .NET programming. His two most recent books are Exceptional C++ Style and C++ Coding Standards (forthcoming October 2004). Jim Hyslop is a senior software designer for Leitch Technology International. He can be reached at jhyslop@ ieee.org.


Let me give you a word of advice, pardner," Wendy's voice floated over the cubicle wall.

"Hey, welcome back," I called as I gophered up. "How's things in the UK office?"

"The usual," Wendy waved me off. "As I was saying though, my advice is: Never trust a home-built AC voltage adaptor." I cocked an eyebrow quizzically. "You know that in the UK everything runs on 230 volts, right? Well, Tom thought he'd save a bit of money and build a voltage converter for my hair dryer, rather than buying one. Only one problem—he wired it up backwards, and I fried my hair dryer the instant I turned it on. Poof!"

"Aw, that's too bad," I commiserated. "I'm glad you're back, though. I've been wrestling with something I think you can help me with. Come on over after you're settled in."

A short while later, Wendy joined me in my cubicle.

"The problem is," I said as I called up the code I had been working on, "I'm deriving my class from two different functions, each of which has a virtual function with the same name. I've distilled it down to this."

class Base1
{
public:
  virtual void VirtFunc()
  {
    cout << "Base1::VirtFunc" << endl;
  }
};
class Base2
{
public:
  virtual void VirtFunc()
  {
    cout << "Base2::VirtFunc" << endl;
  }
};

class D : public Base1, public Base2
{
public:
  virtual void VirtFunc()
  {
    cout << "D::VirtFunc" << endl;
    Base1::VirtFunc();
    Base2::VirtFunc();
  }
};
void f( Base1 & b );
void g( Base2 & b );

"When I call f, I need it to call Base1::VirtFunc, and when I call g, I need it to call Base2::VirtFunc. But, I can't find a way to override both versions of the function."

"Why not rename one or both functions in the base class so they more accurately reflect what they do? You know, good naming conventions, and all..."

"All the Base1 stuff is in a different library from all the Base2 stuff. Third-party libraries. And besides, even a good name can clash—like Draw or Write."

"Yeah, okay. Well, I think we already talked about your solution, pardner." Wendy smirked at me.

"We have?" I scratched my head.

"Sure—only I hope your adaptor won't zap things like mine did," she said.

"Adaptor? Zap?!? Oh, I get it—create an intermediate class to act as an adaptor. Are you sure?" I didn't wait for an answer, but turned back to the keyboard and started typing:

class Base1Intermediate : public Base1
{
public:
  void VirtFunc()
  {
    cout << 
      "Base1Intermediate::VirtFunc"
      << endl;
  }
};

class Base2Intermediate : public Base2
{
public:
  void VirtFunc()
  {
    cout << 
      "Base2Intermediate::VirtFunc" 
      << endl;
  }
};

"But how do I override VirtFunc in the main class—D?"

"You don't," Wendy said as she took the keyboard from me. "Just to show the difference, I'm going to create a new class—D2."

class D2 : 
  public Base1Intermediate, 
  public Base2Intermediate
{
};

"Notice I didn't override VirtFunc—that's the key. When you pass the D2 to f, the final overrider for it is Base1Intermediate's VirtFunc. Same idea for g." She then added some more implementations:

void f( Base1 & b )
{
  cout << "In function f()\n";
  b.VirtFunc();
  cout << "\n";
}

void g( Base2 & b )
{
  cout << "In function g()\n";
  b.VirtFunc();
  cout << "\n";
}
int main()
{
  D d;
  cout << "In main, with d\n";
  f( d );
  g( d );

  D2 d2;
  cout << "In main, with d2\n";
  f( d2 );
  g( d2 );
  return 0;
}

I looked it over, and then we compiled and ran the program:

In main, with d
In function f()
D::VirtFunc
Base1::VirtFunc
Base2::VirtFunc

In function g()
D::VirtFunc
Base1::VirtFunc
Base2::VirtFunc

In main, with d2
In function f()
Base1Intermediate::VirtFunc

In function g()
Base2Intermediate::VirtFunc

"Hey, that's pretty cool," I said. "But what if we also wanted to have an override that calls both functions?" We jumped at the customary snap that I immediately recognized as the Guru's.

"Then decide which personality you want to present, you must," the Guru said softly. "Consider, my child, what you have already bespoken. When in the context of a Base1, your class must act like a Base1. When in the context of a Base2, it must act like a Base2. When in the context of its own, then it must find its own place by presenting its own function, which then calls the two virtual functions." She picked up a marker and wrote on the whiteboard:

void D2::NonVirtfunc()
  {
    Base1Intermediate::VirtFunc();
    Base2Intermediate::VirtFunc();
  }

"The function may, of course, be virtual if necessary."

"So, now that you're here," I grinned mischievously, "is there any way to avoid the intermediate classes?"

"Not really, my child," the Guru shook her head. "Although it has been suggested that an extension to the language could permit it thusly:"

class Fantasy : public Base1, public Base2
{
public:
  void VirtFunc1() overriding Base1::VirtFunc;
  void VirtFunc2() overriding Base2::VirtFunc;
};

"As the syntax suggests—and I must emphasize, my children, that this is pure fantasy and is not, to my knowledge, being considered by the disciples on the ISO C++ Standards committee—VirtFunc1 would override Base1's function, and be used in the context of Base1. Similarly, VirtFunc2 would override Base2's function, and be used in the context of Base2.

"Until such time as fantasy becomes reality," she said as she turned and glided away, "the intermediate adaptors are a good mechanism for achieving your goal."

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.