Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Conversations: By Any Other Name


November 2000 C++ Experts Forum/Conversations


Hanging barely a hundred kilometers distant, the small planet filled a fair portion of our field of vision through the viewport. The surface looked like a field of cracked glass repaired with icy glue. Many frozen rivers wended their way among low crisscrossing ridges. Europa was as beautiful as it was inhospitable.

The speaker said, "Embark, sections three and seven," and fell silent.

Jeannine and I huddled near the viewport among a small gaggle of fellow mission specialists, the lot of us probably looking for all the world like a bunch of slackjawed cadets. We didn't care. Better views were available on any screen in the ship, of course, but there's always something special about seeing the real, original light, reflecting straight from the moon to your eye.

"Nice place to visit," Jeannine volunteered. "They're keeping a tight lid on the rumors, still."

"'Tight' doesn't begin. Ganymede this, Ganymede that. They could have told us the real destination."

"Could've. I'm sure we'll find out soon what they discovered down there, under the ice."

"Mmm? Mmm." I reflected. "I'm happy just to get to be a visitor here. I've seen this picture before as a slicked-up virtual, but this is just as good — no, better. We're really here. This reminds me of my first job."

She snorted playfully. "Everything reminds you of your first job."

"So I was young and impressionable. Anyway, it's just this talk of visiting that made me think of it again just now."

"Ah." A pause, as the hook settled in, then: "What do you mean?"

I smiled, and proceeded to explain...


I had been struggling with a design problem. I really wanted to solve it on my own, because my probation period was almost over and I wanted to prove myself.

You remember your first job after graduating, don't you? How important it was to do things right? I kept remembering the other recruits who hadn't made it through probation because they couldn't deal with the Guru. Don't get me wrong, she was an excellent programmer, just a little... odd. Now here she was my mentor, no less, and I wanted badly to prove myself to her.

I needed a new virtual function in a class hierarchy, but another team maintained the hierarchy and wouldn't let anyone else change it. It looked something like:

class Personnel
{
public:
  virtual void Pay ( /*...*/ ) = 0;
  virtual void Promote( /*...*/ ) = 0;
  virtual void Accept ( PersonnelV& ) = 0;
  // ... other functions ...
};

class Officer : public Personnel { /* override virtuals */ };
class Captain : public Officer { /* override virtuals */ };
class First : public Officer { /* override virtuals */ };

I wanted a function that did one thing if the object was a Captain, and another if the object was a First officer. A virtual function would have been just the thing, declared in either Personnel or Officer and overridden in Captain and First.

Trouble was, I couldn't add one. I knew I could've worked around the problem by simply adding a type check using run-time type information, like this:

void f( Officer &o )
{
  if( dynamic_cast<Captain*>(&o) )
    /* do one thing */
  else if( dynamic_cast<First*>(&o) )
    /* do another thing */
}

int main()
{
  Captain k;
  First s;
  f( k );
  f( s );
}

But I knew that such type checks were frowned upon in the company's coding standards. "I don't like it," said I to myself, said I, "but I guess I'll be able to justify it this time. There's obviously no other way."

"Every problem can be solved by adding another layer of indirection."

I jumped at the Guru's voice behind me. "Pardon?" I asked, turning toward her.

"Every probl—"

"Er, yes," I interrupted her, forgetting myself. "I heard what you said, I just don't get where you're coming from." Boy, that's an understatement, I thought. Neither does half the department.

"Ah, but you will." The Guru leaned toward me. "You will, my young apprentice." She stared at me intently for a moment, then straightened up. "My child, disciples who use C often use a switch statement to direct the program flow based on the object type. Consider this parable." She picked up the dry-erase marker:

/* A not-atypical C program */
void f(struct someStruct *s)
{
  switch(s->type) {
  case APPLE:
    /* do one thing */
    break;
  case ORANGE:
    /* do another thing */
    break;
  /* ... etc. ... */
  }
}

"When those same disciples study the prophet Stroustrup," she continued, "thereby learning C++, they must learn how to design good class hierarchies."

"Yes," I interrupted again, eager to show that I really did know something. "They'd create a class hierarchy with Fruit as the base class, and derived classes Apple and Orange. The 'do something' code would be virtual functions in the derived classes."

"Very good, my child. Very often, C++ disciples will use the parable I have shown to proselytize, by pointing out that the switch statement is an error-prone maintenance chore. There is, however, another way of looking at this parable: by introducing a virtual function, you have added a layer of indirection." She lay down the marker. "What you want is a new virtual function."

"Aha. Right. I know that, and," I concluded triumphantly, "I can't do that here."

She nodded. "Because you may not modify the hierarchy. Yes."

That stopped me, but only for a moment. "Oh, you know? That we can't change it? Then you understand."

"Oh, yes. I designed that hierarchy."

"Oh." That stopped me completely.

"This hierarchy must be kept more stable than most because of unusual cross-system dependencies, but it is designed to let you add virtual functions so as to avoid type-switching code. You can solve your problem by adding another layer of indirection. Consider the parable yet again. What is Personnel::Accept?

"Uh, um?" I said rather dimly.

"This class implements the Poorly Named Pattern, or PNP. It is also known as the Visitor pattern [1]."

"Uh, um!" I said, a little more brightly now. "I've read about Visitor. But that's just for having objects that can iteratively visit each other. Isn't it?"

She sighed. "A common misapprehension. Think that the V means, not so much Visitor, but Virtual," she explained. "The PNP's most useful application is to enable the addition of virtual functions to an existing class hierarchy without further changing that hierarchy. Note first how Accept is implemented in Personnel and its children." She pulled up code like the following:

void Personnel::Accept( PersonnelV& v )
  { v.Visit( *this ); }

void Officer::Accept ( PersonnelV& v )
  { v.Visit( *this ); }

void Captain::Accept ( PersonnelV& v )
  { v.Visit( *this ); }

void First::Accept ( PersonnelV& v )
  { v.Visit( *this ); }

"And," she continued, "the Visitor base class is thus:"

class PersonnelV/*isitor*/
{
public:
  virtual void Visit( Personnel& ) = 0;
  virtual void Visit( Officer& ) = 0;
  virtual void Visit( Captain& ) = 0;
  virtual void Visit( First& ) = 0;
};

"Ah," I said, as I remembered. "So when I write code that uses Personnel-derived objects polymorphically, I just call Personnel::Accept( myVisitorObject ), and because Accept is virtual myVisitorObject.Visit will be called with the right derived type, and overload resolution picks the right function. That is sort of like adding a new virtual function, isn't it?"

"Indeed, my child. The newly added function normally must use only the client class's public interface, but in all other respects it can and does behave virtually even though not a member. All that is needed is that the original hierarchy support Accept, so as to open itself for extension. If in the future we find that we often extend the visited Personnel hierarchy with new child classes, or we want to better manage recompilation dependencies, we may migrate to Acyclic Visitor — without any change to Personnel and its little ones beyond minor restatement of their Accept functions [2, 3]."

I got it. "Okay, so I can do what I was trying to do like this." I quickly wrote:

class DoSomething : public PersonnelV
{
public:
  virtual void Visit( Personnel& );
  virtual void Visit( Officer& );
  virtual void Visit( Captain& );
  virtual void Visit( First& );
};

void DoSomething::Visit( Captain& c )
{
  if( femaleGuestStarIsPresent )
    c.TurnOnCharm();
  else
    c.StartFight();
}

void DoSomething::Visit( First& f )
{
  f.RaiseEyebrowAtCaptainsBehavior();
}

The Guru peered at my code more intently. "What system did you say this code was for?"

"Ah... a simulator?"

"I see. Well, go ahead, child, write the rest." I added an example test harness:

void f( Personnel& p )
{
  p.Accept( DoSomething() ); // like p.DoSomething()
}

int main()
{
  Captain k;
  First s;

  f( k );
  f( s );
}

The Guru stepped back and pushed a graying lock behind one ear. "True, the function does not directly inhabit the original hierarchy with a Personnel:: scope name. And yet, in this instance, a function by any other name is still as virtual," she smiled, and glided away. Her words drifted off with her as she passed out of sight: "And a Visitor pattern by any other name is still as useful, and is more aptly described. But that is the way of things...."


The massive bulk of Jupiter, crushingly majestic and dwarfing the beauty of Europa's surface, began to shoulder its way into the viewport scene as the ship rotated.

"I'm going to put in for some surface suit time," I decided. "What a sky to walk under. Even if I am just a visitor here."

"Visitors we are," Jeannine agreed. "But I wonder, are we the first?"

Then, for a time, we merely watched in silence.

Notes

[1] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995).

[2] R.C. Martin. "Design Patterns for Dealing with Dual Inheritance Hierarchies," C++ Report, April 1997). Available online at http://www.objectmentor.com/publications/dih.pdf.

[3] R.C. Martin, "Acyclic Visitor," in R.C. Martin, D. Riehle, F. Buschmann (eds.), Pattern Languages of Program Design 3 (Addison-Wesley, 1998). Available online at http://www.objectmentor.com/publications/acv.pdf.


Jim Hyslop is a senior software designer at Leitch Technology International Inc. He can be reached at [email protected].

Herb Sutter is chief technology officer of PeerDirect Inc. and secretary of the ISO/ANSI C++ standards committee. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.