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

Conversations: Virtually Yours


December 2000 C++ Experts Forum/Conversations


I gave the wrench another twist, but it was useless. I could have tried pounding the wrench with the hammer to try to force it over, but that didn't seem like a good idea. "It won't budge," I said. "There's almost enough room for the new module, but what's in there already is all just wedged in too tight."

"Kee-ristopher," Jeannine complained, watching because there was only room for one of us to work on the machine. "It can't be that hard to extend the hardware."

"It sure can be when it's not put together right," I sighed. "Whoever slapped this together must've figured that as long as it worked it was good enough. Everything's been put together too tightly coupled. We can extend it, all right, but first we'll have to take it all the way apart and rebuild it."

"Welcome to Europa, the pull-your-own-weight capital of the system. Back home we'd have techs doing this for us."

I mopped my brow. "You know," I said thoughtfully, "this reminds me..." Jeannine smiled; that was the signal for a caffeine break, and I began to tell her another story from my first job.


I was working on some code that Wendy, the programmer in the next cubicle, had designed. She had created an abstract base class for the project, and my job was to derive a concrete class from it.

As usual when studying a new class, I began with the public interface:

class Mountie
{
public:
    void read( std::istream & );
    void write( std::ostream & ) const;
    virtual ~Mountie();

No surprises here — a virtual destructor, implying that this is intended to be used as a base class. Non-virtual read and write members, though — I wanted to look into that one.

Then I moved on to the protected interface:

protected:
    virtual void do_read( std::istream & );
    virtual void do_write( std::ostream & ) const;

After a moment, I realized that Wendy was using the Template Method pattern: the public, non-virtual functions clearly would invoke the protected virtual functions [1]. Pleased with my own quick understanding, I was feeling pretty smug by this point — ready to handle anything that would be thrown at me. Until I looked at the private interface:

private:
    virtual std::string classID() const = 0;

I stopped cold. A pure virtual, private function? How could it possibly work? I gophered up.

"Um, Wendy," I said, "your Mountie class can't work. It's got a private virtual function."

"Have you tried it?" Wendy asked, without looking up from her keyboard.

"Uh, well, no," I admitted. "But my derived class can't possibly override the classID function."

"So certain are you?" The Guru's quiet voice startled us both. "Always with you it cannot be done. Have you learned nothing from me in these months you have been my apprentice?"

Despite the calm quiet of the Guru's voice, I was taken aback by the force of her words.

"My child," she continued, "you have forgotten that access privilege and virtualness are independent of each other. Determining whether the function is to be statically or dynamically bound is the last step in resolving a function call. You need to read and meditate upon the Holy Standard, chapter 3.4 verse 1, and chapter 5.2.2 verse 1 [2]."

I decided it was time to demonstrate my brilliance. I opened my mouth... "Oh, yeah. Uh, right." So much for brilliance. I decided to take another tack — distraction. "But I still don't understand why it's private. It just doesn't make sense to me."

"Meditate upon this parable, my apprentice. You are creating a class, and after reflecting on the design, you decide the class requires a member function that should not be called by any other classes, not even its derived classes. What do you do?"

"You make it private, of course," I replied. The Guru looked at me, her eyebrows raised expectantly. My brain kicked into overdrive trying to figure out how to link together private and virtual functions.

Wendy spoke up. "Have you examined the implementation of the Mountie class, especially the write function?"

I quickly turned to my keyboard, glad to briefly escape the Guru's unrelenting gaze. I found the function fairly quickly:

void Mountie::write(std::ostream &Dudley) const
{
    Dudley << classID() << std::endl;
    do_write(Dudley);
}

I could tell Wendy had spent way too much time watching cartoons as a child. "I see now," I said. "classID is an implementation detail, used to signal the concrete type of the class that is being saved. Derived classes must override the function, but since it is an implementation detail, it's kept private."

"Good, my child," the Guru agreed. "Now explain to me why do_write and do_read are not private."

That stopped me for a moment. But then the penny dropped: "Because the derived class has to call its parent class's implementation of those functions so that the parent class has a chance to read and write its data."

"Very good, my apprentice," the Guru was almost beaming. "But why not make them public?"

"Because," I was really warming up to this now, "they must be called in a controlled fashion, especially the do_write function. The object type has to be written to the stream first so that when the object is read in, the factory knows which type of object to instantiate and then load from the stream. Allowing public access to the functions would cause chaos."

"Very wise, my apprentice." The Guru paused a moment. "As with spoken languages, there is more to knowing the C++ language than just knowing the grammar and the rules. You must know the idioms."

"Yes, Coplien's book was the next one on my— [3]"

The Guru held up her hand. "Peace, child. I do not refer to the prophet Coplien. I refer to the linguistic sense of the word idiom — the implied meaning behind certain constructs. You know, for example, that a virtual destructor tells you 'I am meant to be used as a polymorphic base class, you may create from me little ones as you need' whereas a non-virtual destructor tells you 'I am not meant to be used as a polymorphic base class, do not derive from me for that reason, I pray.'

"In a similar manner, the access privilege given a virtual function conveys an idiomatic meaning to the initiated. A protected virtual member tells you 'my little ones should — or perhaps even must — invoke my implementation of this function.' A private virtual function tells the initiated 'my little ones may or may not override me as they choose, however they may not invoke my implementation.' "

I nodded as I absorbed this. "What about public virtual functions, though?"

"Avoid them where possible, and prefer to use the Template Method. Consider this parable." She picked up the dry erase marker, and began writing in her delicate script:

class HardToExtend
{
public:
    virtual void f();
};
void HardToExtend::f()
{
   // Perform a specific action
}

"Consider that you have released this class, and your requirements have changed," she continued. "In the second release of this class, you discover you need to implement the Template Method on the function f(). It will be nigh on impossible to accomplish. Can you see why?"

"Uh, um. Yes, well... I think... No, I can't."

"There are two possible ways to convert this class to the Template Method. The first way would be to move the implementation code of f() into a new function and make f() non-virtual, like this:

class HardToExtend
{
// possibly protected
    virtual void do_f();
public:
    void f();
};
void HardToExtend::f()
{
    // pre-processing
    do_f();
    // post-processing
}
void HardToExtend::do_f()
{
    // Perform a specific action
}

"However, HardToExtend's little ones will expect to override f(), not do_f(). You must now change all classes derived from HardToExtend. If you miss just one class, that class will attempt to override a non-virtual function. This could introduce, in the words of the prophet Meyers, 'schizophrenic behavior' into your class hierarchy [4].

"The other solution is to introduce a new, non-virtual function and move f() to the private section, like this:"

class HardToExtend
{
// possibly protected
    virtual void f();
public:
    void call_f();
};

"This will cause users of the class no end of headaches. All the clients of HardToExtend will attempt to invoke f(), not call_f(). Those clients will no longer compile. Furthermore, derived classes will most likely leave f() in the public interface, and clients that use those derived classes directly, rather than using a pointer or reference to HardToExtend, will still have direct access to the function you want to protect.

"Virtual functions should be treated very much like data members — make them private, until design needs indicate a less restricted approach is indicated. It is much easier to promote them to a more accessible level, than it is to demote them to a more private level."


"When did all this happen?" Jeannine asked.

"Just before the turn of the millennium — the real turn, that is. It was at the end of 2000, I think. We were just about to celebrate New Year's Day, and we were really looking forward to the special year of 2001, because of all the literature, you know..."

Oddly, it was shortly after that conversation that we heard about the discovery of the obelisk under the surface of Europa.


Notes

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

[2] ISO/IEC 14882:1998, "Programming Languages — C++," clauses 3.4 and 5.2.2.

[3] J. Coplien. Advanced C++ Programming Styles and Idioms (Addison-Wesley, 1992).

[4] S. Meyers. Effective C++: 50 Specific Ways to Improve Your Programs and Design, 2nd edition (Addison-Wesley, 1998); Item 37: "Never redefine an inherited non-virtual function."

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.