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:
interface, and points out a schism in the focus of the C++ and other prominent development communities.
Bill Venners: One of your guidelines in Effective C++ is, "Use const wherever possible." I followed this guideline for a while when I programmed in C++, but I always felt I was typing a lot without getting much benefit. In Java, I don't use final wherever possible. For example, I usually don't declare method parameters final, even if I think they should never be changed. The reason is that 99.9 percent of the time they don't get changed anyway, so I don't feel typing const or final all those times is worth the benefit. Had you ever heard that feedback before?
Scott Meyers: Actually, I've never heard that particular argument before. Const is an insurance policy. It's a way of saying to the compiler, "Don't let me change this, because I'm not supposed to change it." Is the insurance worth the premiums? You're saying in your experience, the cost of the policy to type const all those times just doesn't pay for itself. If that's been your experience, then that's fine.
Everybody I have talked to about const has told me essentially the same story. It is possible the stories have sought me out. I'm not saying this is a random sample. The story basically goes like this: Our system was not const correct. We didn't use const, so we decided to add const. It was a hellish nightmare to do it, because const propagates all over the place. You can't just fix it in one place. You have to fix it throughout the entire system. Everybody said they found a few places where they were modifying things that were never supposed to be modified. So they actually did track down logic errors.
Now one can wonder how serious the logic errors were if they never caught them in testing. One might also question how good their testing was. Nevertheless, people who have used const in large systems have reported that they had identified places where they would have violated a design constraint. I'm still a big believer in const, because I think typing those six characters just isn't that hard to do.
Bill Venners: You said in the first edition of Effective C++:
Fortunately, C++ provides no way to determine at runtime the actual type of object a pointer points to, and let me assure you this is no accident. In fact, runtime type inquiry (RTTI) was explicitly omitted from the language to prevent abuses such as this.
Doesn't C++ have RTTI now?
Scott Meyers: Yes.
Bill Venners: When is it appropriate to use RTTI?
Scott Meyers: Let me give you some history on why the committee added RTTI to the C++ language. This is one of those cases where the consumers won. At the time, every non-trivial class library being written was rolling its own RTTI, without exception. Every major class library used at the time, including MFC (Microsoft Foundation Classes) and NIHCL (National Institute of Health Class Library), had RTTI of some form or another. So the committee just said, "Rather than have N different ways to accomplish the same thing, we're going to have a single language mechanism that does the right thing." That's why RTTI was added. It was a simple matter of people wanted it.
Once the committee added RTTI, people started wondering when it was appropriate to use RTTI. All the bad things I wrote about RTTI in the first edition of Effective C++ continue to apply. They're still bad. So you end up with a classic engineering tradeoff. You use RTTI when the alternatives are worse.
The two most common ways to eliminate RTTI are to add virtual functions higher in the hierarchy and to hold onto a derived class interface by not promoting the derived class type to its base class type. For example, if you can replace RTTI with virtual function calls by modifying a base class, that's usually a better way to go. The other approach, eliminating the need to upcast in the first place, is especially appropriate in C++, because C++ has type-safe containers. C++ has no
Object class like Java or C#. So instead of having, for example, a container of base class pointers, you can have three containers, one for each different type of derived class you're dealing with. Then you'll never lose the type information and you'll never have to do the downcast, so you don't need RTTI.
In many cases, however, you can't do either of those two things to avoid using RTTI. Perhaps you will lose type information, because you will promote a derived class pointer or reference to a base class type. Perhaps you can't modify the base class to add virtual functions, or it just doesn't make any sense to add virtual functions to the base class. What do you do? My advice is, use a safe downcast. C++ has RTTI in the language now. You can do a downcast, and it will tell you at runtime whether the downcast succeeded.
Bill Venners: The C++ community is very efficiency minded. Could you give programmers your idea of what is a healthy attitude towards efficiency? Performance is very important, but so are easy-to-understand designs. How do you decide when to mutate a design and make it more confusing or harder to maintain—or whatever the tradeoff may be—to make it faster?
Scott Meyers: I have a two-pronged approach to efficiency. First, when you're designing the application, you have to consider efficiency. I'm assuming that you, as the designer, are designing something you know something about. You must make some educated guesses about where you think performance bottlenecks will and will not exist. That means efficiency might appear early in the design, just because you know certain things about the application in general. I don't think it's inappropriate to keep efficiency in mind when you design. You don't want to design something that will be inherently inefficient.
The first prong of my two-pronged approach, then, is to keep efficiency in mind from the beginning. Don't completely ignore efficiency up front in favor of fine tuning later. You shouldn't bend over backwards for efficiency up front. Design so things will be efficient enough most of the time. Shoot for clear, straightforward, and maintainable code, but try to avoid introducing gratuitous inefficiencies.
The second prong is fine tune later with profiling. If you find some bottlenecks in the program by running it and profiling, then go back and rewrite the presumably localized parts of the code to improve efficiency.
Serious efficiency problems arise when people design systems with the attitude that, "We're just going to worry about correctness first. We won't even think about efficiency until later." The results have been pretty disastrous.
This is language-independent advice. No matter what language you program in, you should never design something that you know just by looking at it that it won't be efficient enough.
Bill Venners: What are you working on these days? What are you thinking about?
Scott Meyers: Right now I'm trying to identify language- independent principles that improve software quality. My work in C++ largely consisted of trying to identify specific rules that C++ programmers could apply to improve their software's quality, behavior, efficiency, things like that. I'm now trying to look for much broader guidelines that almost anybody working in software development can apply. So it's not limited to C++ or even OO any longer.
I would like to believe we can agree on certain broad principles, like "Code duplication is bad." That's a general principle I hope would apply pretty much anyplace.
Another broad principle would be, "Classes should be easy to use correctly and hard to use incorrectly." That's actually from my C++ books, but you can generalize it. Let me use the word component to mean something you interact with. It might be a class, a function, a library, or a framework. If I could use the word component to mean any of those things you interact with, then I would say a broad principle is that components should be easy to use correctly and hard to use incorrectly. That is a relatively strong constraint on the component designers, because it says if someone uses your component incorrectly, it is probably your fault. It says the onus is on you to ensure that if the code compiles, it does the right thing.
Those are examples of broad principles. Currently, I'm trying to identify a fairly small number of broad principles. I've written two books with 50 ways to improve things and one book with 35. That's 135 principles to remember. I now believe that 135 principles is too many. I would like to have broader, more universal guidelines that are much fewer in number. I'm trying to identify what those are and figure out how to apply them in different languages and different environments so that the advice becomes practical.
Effective C++, Second Edition, by Scott Meyers is at Amazon.com at:
More Effective C++, by Scott Meyers is at Amazon.com at:
Effective STL, by Scott Meyers is at Amazon.com at:
Effective C++ CD, by Scott Meyers is at Amazon.com at:
Scott Meyer's website contains links to many publications, presentations, books, and other information from Scott:
Images of Persephone, the best dog in the world:
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.