Meaningful Programming

A Conversation with Scott Meyers, Part III

by Bill Venners
December 30, 2002

Summary
Scott Meyers, C++ expert and author of numerous books, including Effective C++, talks with Bill Venners about the importance of saying what you mean and understanding what you say, the three fundamental relationships between classes, and the difference between virtual and non-virtual functions.

Scott Meyers is one of the world's foremost experts on C++ software development. He wrote the best-selling Effective C++ series (Addison-Wesley): Effective C++ (1997), More Effective C++ (1995), and Effective STL (2001); wrote and designed the Effective C++ CD; is consulting editor for Addison-Wesley's Effective Software Development Series; and is a member of the advisory board for Software Development magazine. A programmer since 1972, Meyers holds an M.S. in computer science from Stanford University and a Ph.D. from Brown University.

In this four-part interview, which is being published in weekly installments, Meyers gives his views on many object-oriented design topics:

  • In Part I: Multiple Inheritance and Interfaces, Meyers indicates how his view of multiple inheritance has changed with time, describes the C++ community's take on Java's interface, and points out a schism in the focus of the C++ and other prominent development communities.
  • In Part II: Designing Contracts and Interfaces, Meyers discusses interface contracts, private data, and designing minimal and complete interfaces.
  • In this week's installment, Meyers discusses the importance of saying what you mean and understanding what you say, the three fundamental relationships between classes, and the difference between virtual and non-virtual functions.

Say What You Mean

Bill Venners: One guideline you include in Effective C++ is, "Say what you mean; understand what you're saying." You make the point that it's not enough to understand language syntax. You have to understand what you mean when you use the syntax. Why do you think that is important?

Scott Meyers: You are communicating with two different parties when you write a program: the compiler and other human beings. The language behaves in particular ways because constructs have certain kinds of meaning. For example, when I say public inheritance means IS-A—as in an ArrayList IS A List—that is reflected in the language's explicit conversion rules. If you're trying to make public inheritance mean something other than IS-A, you're then fighting the language, because it will probably perform conversions that your semantic model would not normally allow. If you want to represent something using a language mechanism, and you don't understand to what the language mechanism corresponds, the language will not enforce the rules that you want enforced for the particular semantics that you need. One reason understanding meaning is important, therefore, is you are communicating with the compiler and you don't want to fight the language. You want the language to work for you and back up your semantics.

You also want to communicate with other human beings. As a human being, if I'm reading your code and I see that a class publicly inherits from that class, I will think, "This is an IS-A relationship." If that's not what you meant, I'll be confused. Or if I see a non-virtual member function, I will think, "This is an invariant. Nobody should ever override this." If I see it has been overridden in a derived class, I see a contradiction in your design. I no longer trust my understanding of your design, because I'm getting conflicting pieces of information. That makes it more difficult for me to understand what your program is supposed to do. So meaning is important because first, you don't want to ascribe semantics to a language feature that are different from what the compiler will enforce. And second, you don't want to miscommunicate what you're trying to express to other people who read your code.

HAS-A Versus IS-IMPLEMENTED-IN-TERMS-OF

Bill Venners: In Effective C++, you state that the three common relationships between classes are IS-A, HAS-A, and IS-IMPLEMENTED- IN-TERMS-OF. IS-A really helped me understand inheritance. HAS-A, as in a Person HAS A Name, didn't really help me much to understand layering—what I usually call composition. You're other layering relationship, IS-IMPLEMENTED-IN-TERMS-OF, although a mouthful, made sense to me. Is that still how you describe the different ways to related classes? Has anything changed in the last 10 years in how you would describe the basic class relationships?

Scott Meyers: In terms of the vocabulary I introduce in the book, nothing has changed. In fact, I didn't change any vocabulary in the second edition of Effective C++. I still think of those terms as the three fundamental relationships between classes.

Bill Venners: Please differentiate HAS-A and IS- IMPLEMENTED-IN-TERMS-OF.

Scott Meyers: When you write software, you deal with two worlds. You deal with the world you want to model, the outside world. You also deal with a world that exists only inside the software, which involves just getting the code to work. HAS-A corresponds to something in the real world. A Car HAS Wheels or a Person HAS Friends. HAS-A corresponds to the application domain. IS-IMPLEMENTED-IN-TERMS-OF never exists in the real world; it is part of the implementation domain. So you couldn't say a Car IS-IMPLEMENTED-IN-TERMS-OF Wheels. A Car HAS Wheels. But you could say the ParkingLot IS-IMPLEMENTED-IN-TERMS-OF a List. There's no List in the real world. The List only exists inside the software. So HAS-A is a relationship between classes that exists in the application domain. IS-IMPLEMENTED-IN-TERMS-OF is a relationship between classes that exists in the implementation domain.

Bill Venners: That's great. That clarifies it for me.

Scott Meyers: It took me a few years to figure that out myself, actually.

Virtual Versus Non-Virtual Functions

Bill Venners: This past spring I attended a .Net seminar at Microsoft. It was my first visit to Microsoft. I picked up many interesting subtexts, one of which concerned pragmatism versus idealism. One Microsoft manager said to me, "Microsoft is a company of pragmatists." I sensed a somewhat dismissive attitude at Microsoft of people who were too academic or idealistic. At times I got the sense they felt Java tries to be too pure or academic, and that C# is more pragmatic.

One specific example of this pragmatic attitude involved virtual and non-virtual functions. In Java, every instance method is dynamically bound, or virtual in C++ jargon. You can mark an instance method final in Java, in which case subclasses can't override it. But there's really nothing like a non-virtual instance method in Java.

In C++ and C#, you have a choice between virtual and non-virtual member functions. In both languages, the default is non-virtual. You have to specifically mark the function with a virtual keyword to achieve dynamic binding. Anders Hejlsberg, C#'s main designer, claimed that making virtual the default, as it is in Java, is wrong. I'm curious what you think of that philosophical difference between the Java designers and the C# designers. What do you think should be the default and why? Also, how would you suggest designers using C++ and C# choose between virtual and non-virtual functions?

Scott Meyers: Let me answer the second question first. A non- virtual function is an assertion. It asserts that this method applies to this class and any derived classes that may exist; furthermore, you have to use this implementation. It asserts that if this implementation is not appropriate, you should not inherit from this class. A non-virtual function adds a new constraint to derived classes. It says, you have to do this, and you have to do it this way. If you don't want to do it this way, you can't inherit from me. You're not an IS-A relationship with me anymore.

Bill Venners: That's what final does in Java. When you add final to the method, subclasses can't override it. It's the same constraint.

Scott Meyers: It's exactly the same constraint. Unlike Java, C++ has no language mechanism to enforce that constraint. In Java, if you then try to override a final method in a derived class, the compiler would reject the code. C++ can express the constraint. That's what a non-virtual function is. But C++ doesn't have a language mechanism to enforce the constraint. You can play stupid C++ tricks and enforce it, but no one does that in practice.

A virtual function means the interface to the function applies to this class. By interface, I mean the function name, the types and order of the parameters, the return type, and most importantly, the semantics. A virtual function's interface applies to this class and all derived classes. That will never change. However, the implementation, if one is provided, because it could be a pure virtual function, is a default implementation. It may be overridden if you want to accomplish exactly the same thing in a different way. Because what the function accomplishes is fixed. That's the semantics. You can't play games with the semantics. If you do, you violate the IS-A relationship.

A non-virtual function says, you have to do this and you must do it this way. A virtual function says you have to do this, but you don't have to do it this way. That's their fundamental difference.

Regarding the C# decision that the default should be non-virtual rather than virtual, I can't put words in the designer's mind. I can tell you exactly why it's the default in C++.

Bill Venners: Why is non-virtual the default in C++?

Scott Meyers: Because it's more efficient. Because it's not dynamically bound, right? More important, this means that if you have a class in C++, and all member functions are non-virtual, then you will not have a vtbl (virtual table) or a vptr (virtual table pointer). So the object, as well as your overall application data space, is smaller. The decision to make functions non-virtual by default strongly ties in with the concern for efficiency.

The decision to make functions non-virtual by default in C++ also ties in with the concern for C compatibility. In C++, certain member functions are automatically generated, even in structs. You get an assignment operator if you ever do an assignment. You get a copy constructor if you make a copy. You get a destructor if you ever create an instance, at least on the stack or statically. And it is important that those functions are non-virtual, because if they were virtual, then you would change the object's layout and it would no longer be compatible with C.

Resources

Effective C++, Second Edition, by Scott Meyers is at Amazon.com at:
http://www.amazon.com/exec/obidos/ASIN/0201924889/

More Effective C++, by Scott Meyers is at Amazon.com at:
http://www.amazon.com/exec/obidos/ASIN/020163371X/

Effective STL, by Scott Meyers is at Amazon.com at:
http://www.amazon.com/exec/obidos/ASIN/0201749629/

Effective C++ CD, by Scott Meyers is at Amazon.com at:
http://www.amazon.com/exec/obidos/ASIN/0201710919/

Scott Meyer's website contains links to many publications, presentations, books, and other information from Scott:
http://www.aristeia.com/

Images of Persephone, the best dog in the world:
http://www.aristeia.com/Persephone/index_frames.html

Talk back!

Have an opinion? Readers have already posted 2 comments about this article. Why not add yours?

About the author

Bill Venners is president of Artima Software, Inc. and editor-in-chief of Artima.com. He is author of the book, Inside the Java Virtual Machine, a programmer-oriented survey of the Java platform's architecture and internals. His popular columns in JavaWorld magazine covered Java internals, object-oriented design, and Jini. Bill has been active in the Jini Community since its inception. He led the Jini Community's ServiceUI project that produced the ServiceUI API. The ServiceUI became the de facto standard way to associate user interfaces to Jini services, and was the first Jini community standard approved via the Jini Decision Process. Bill also serves as an elected member of the Jini Community's initial Technical Oversight Committee (TOC), and in this role helped to define the governance process for the community. He currently devotes most of his energy to building Artima.com into an ever more useful resource for developers.