Scott Meyers on Programming C++
Bill Venners writes "Artima.com has published a four-part interview with Scott Meyers, author of Effective C++, More Effective C++, and Effective STL. In Multiple Inheritance and Interfaces, Scott describes how his view of multiple inheritance has changed with time, the C++ community's take on Java's interface, and a schism of focus between the C++ and other prominent development communities. In Designing Contracts and Interfaces, Scott discusses interface contracts, private data, and designing minimal and complete interfaces. In Meaningful Programming, Scott 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. In Const, RTTI, and Efficiency, Scott describes the utility of const, the appropriate time to use RTTI, a good attitude about efficiency, and Scott Meyers' current quest for general programming principles."
I own each of Meyer's books, and they are all wonderful. If you work with C++ in any sort of professional capacity get a hold of copies right now.
Meyers: "... we have templates in C++, but there's no way to write user interfaces or talk to databases. Java has no templates, but you can write user interfaces up the wazoo and you can talk to databases with no trouble at all."
Stupid job ads, weird spam, occasional insight at
Interfaces in java can have member variables but they are always public (universally accessible), static (not associated with any particular instance of a class) and final (unchanging). Thus, if you use data members in interfaces you cannot hide them in the implementing class, but this is mitigated by them being read only. I have not seen anyone use these inherited constants (which is what they amount to), and I can't think of a major drawback to using them, but I don't know of any advantage either.
Does anyone know why interfaces have data members at all?
"The plural of anecdote is not data." -- Roger Brinner
One reason Java has standard libraries is that Sun pushed big time for standard libraries. C++ Hasn't had anyone pushing for stuff like that. Bjarne Stroustrup says that he favors a minimal standard library, and so in C++ the standard library is minimal.
Interfaces are the primary mechanism where Java has enforced the APIs for JDBC, EJB, Servlet etc.
Now that Java is about to get Templates, I wonder how that will change the way they go about standard APIs, especially for collections.
What libraries would make sense to have in C++? Network stuff, Database access, Web Programming, Graphics (OpenGL, DirectX), Windows Apps (Qt, MFC), XML, Logging, OS abstractions (Memory and Threading come to mind, Semaphore/Mutex, IPC). But the real question is, 'Does the lack of standard libraries help or hurt development in these fields?' I'd venture to say a both POV could be argued.
Open Source Identity Management: FreeIPA.org
Meyers is apparently not the only one thinking that java needs templates: Preparing for Generics
It can be a very useful way of exposing constants. For example, consider the following:-
This will provide you with a slightly more flexible set of constants that are automatically acquired by anything that implemnts the VariableList interface.
Alternatively, you can use Mutable variables. Remember that it is only the references to the variable that is final. If the Object itself contains information then it accessed by all classes that implement the interface. For example:-
If we now stipulate that all instances of CountMe that want to be included in the CountMe functionality should register themselves inside the INSTANCES variable, for example:-
and there you have it - data sharing across interfaces with no extending classes at all. Of course, you cannot force classes to register themselves in the INSTANCES variable, but if you wanted to extend a couple of base classes then it would be trivial to make a couple of implementations of CountMe that extend each of them which enforce the functionality explicitly.
The only Good System is a Sound System
... and this article has helped me to understand precisely why we disagreed. And he's still wrong, although he's getting closer.
I've been writing C++ code professionally since about 91, when Scott published his first book. I think I was lucky that I didn't come across his book for a few years, particularly because of his skepticism of MI. I might have followed his advice and my career would have been the worse for it.
I have always made heavy use of MI in my code, and 80% of the time I've written C++ classes that are exactly analogous to Java interfaces; it just always seemed like a good idea to me. Also, I was a big fan of Robert C. Martin and his notions about how you can analyze design quality by looking at the abstractness of the classes and the dependencies between them (dependencies on abstract classes, especially pure abstract classes(*) are, much better than dependencies on classes that do a lot of stuff for the simple reason that classes that do a lot have more potential to change, so purely abstract firewalls tend to limit the ripple effect).
To avoid self-aggrandizement, I didn't independently invent the notion of pure abstractions. I had fiddled with Objective-C and it had a construct (whose name I forget) that allowed you to define a pure interface. You could then use that pure interface type as a function parameter and the compiler would verify that objects you passed to that function met the interface requirements -- allowing you to get compile-time typechecking in an otherwise completely dynamically-typed language.
However, as I said, I think only about 80% of my MI usage is with pure abstractions. Probably 10% of the time I use MI, I do it to facilitate a style of programming called "mix-in" programming. The idea is that you have a bunch of purely abstract classes that define the potential interfaces, and you have a bunch of concrete classes that implement the pure abstractions in various useful ways and then you can create useful classes by mixing together appropriate base classes (with the occasional bit of glue). Mix-ins aren't appropriate for everything, but they can be a very elegant solution for many toolkit kinds of scenarios. Diamond inheritance doesn't really happen, because none of the mixed-together classes have enough code in them to make it worth inheriting from them. If you need something almost like one mixed-together class, you just mix a completely new one.
In practice, not only do pure abstractions and mix-ins sidestep all of the "problems" that make people leery of MI, they're also not at all confusing to less competent programmers. I've found that with just the tiniest explanation of how the structure is put together, people can see immediately how it works and why it's good (well, once they've understood the idea of polymorphism, anyway).
I think C++ MI can also be used more fully to good effect, but that must, indeed, be done judiciously.
(*) Scott said in the interview that the C++ community doesn't have a name for interface classes. Maybe not, but I've been using the term "pure abstract class" for close to a decade and I don't think I've come across a single marginally-competent C++ programmer who didn't immediately understand the term, and I'm pretty sure I picked the term up from comp.lang.c++.
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
The easiest way of making a cleaner approach to storing the ranges of items in the maps (without having to remember to update lots of locations when you change the contents of your MyEnum[] is to use the contents of the Array itself. So if you just want a List then you would say:-
Now in your example, you are using a Hashtable with a method of MyEnum called getName(). I would be tempted to extract this Nameable method into an interface (Named) and then create a dedicated Map that would look something like this:-
Then you can finish up by adding another constant to your MyEnum class that provides you with the Map functionality that you wanted:-
Now if you change the definition of FIRST_AND_LAST then all of your other constants (List and Map) will get updated automatically thereby ensuring that you preserve the main benefit of utilising constants: only having to remember to Get Things Right Once.
The only Good System is a Sound System
The only Good System is a Sound System
The approach I'm aiming for involves designing quite a large number of fairly minimal classes and plugging them together using MI to get the final behaviour I want.
Yes, that's mix-in programming in a nutshell. Thanks, you stated it better than I did. You can see why "diamond" inheritance isn't a concern in that kind of a structure -- there's really never any need to inherit from a "plugged-together" class; if you need another class with slightly different behavior, you grab your toolbox and plug it tothether.
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
Funny, you had the opposite experience that I had. I discovered Scott's book quite by accident when I first started programming C++. It guided me away from multiple inheritance, which I ended up never using until I turned to Java five years later.
Your comment about Objective-C reminded me of something James Gosling once said in one of my interviews. I went to Artima.com and searched for it, but couldn't find it. To my surprise, the comment wasn't in the article anywhere. I went back to the text file that originally came back from the transcriber in 1999, and there was his comment. Somehow it never got published. So I just published it four years after he said it. Sorry.
Like you, Gosling found inspiration for Java's interface from the corresponding construct in Objective-C. Here's what he said in 1999:
http://www.artima.com/intv/gosling13.html
Like you, Gosling found inspiration for Java's interface from the corresponding construct in Objective-C.
That is very cool...
In case anyone is wondering, the Objective-C construct in question is the "protocol". A protocol is essentially nothing more than a list of methods, with no implementations. A class can indicate that it intends to "conform" to one or more protocols, and the compiler will issue a warning if doesn't implement everything. Methods can also specify that their parameters should conform to a certain set of protocols and object references (id's) can specify that they can only point to objects that conform to a certain set of protocols.
The compiler will perform compile-time type checking wherever it has enough information. So, if you create an object instance and then a few lines later try to assign it to an id or pass it to a method that have a protocol specification, and the object doesn't implement all of the methods required, the compiler will complain. I don't think it's even necessary that the class *specify* conformance to a protocol, the compiler can still check by seeing if all of the required methods are present. Of course, if the compiler doesn't have all of the information it needs, then run-time typechecks are generated.
Protocols were primarily invented, I believe, to make it feasible to detect errors at compile time rather than at run time. In practice, though, anyone who used them quickly discovered that they're also a very effective tool for understanding and defining the structure of a complex program -- and they did it without limiting or constraining the dynamic typing of the language at all.
It's a small step from seeing how protocols can layer structure onto a dynamically-typed language to seeing how they can define structure in a statically-typed language, where the structure must be completely verifiable at compile time because there are limited (or no!) facilities for run-time checks.
My favorite programming language is, and maybe always will be, Objective-C++. It turns out that although Objective-C and C++ are both OO extensions to C, they take completely different approaches, both philosophically and syntactically. In fact, the syntaxes of the extensions are so completely orthogonal that you can just lump them together into a single language, without ambiguity. Since NeXT built their Objective-C compiler on top of gcc, and others were building C++ on top of gcc, the merger was natural.
The result is a language that has all the expressive power and flexibility of a fully dynamically-typed language *and* all of the on-the-metal performance of a statically-typed language designed to be as efficient as C. The programmer gets to choose the tradeoffs between expressiveness and performance on a class by class basis, and can easily mix and match, passing C++ objects to Objective-C class methods and vice-versa.
Of course, the result also has all of the arcane complexity of C++, and although Objective-C is very simple, the resulting design decisions are anything but, since there are two very different views of object orientation to be mixed and matched. For the programmer who has mastered both views and also understands the dusty corners of C++, the combination is extremely powerful and, IMO, wholly appropriate to everything from on-the-metal bit twiddling to rapid development of large, complex applications...
... as long as they can be written by this single programmer, because the odds of finding two people who can agree on enough of the myriad design tradeoffs to get any useful work done is next to nil. And don't even *think* about bringing a novice developer onto the project.
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
He has a Ph.D. Have a nice day.
Seems to me that if your type checking is failing due to different class loaders, you may have a bigger problems than just the enumerations.
THe best thing about the type safe enum is that it can quite possibly grow into a full blown class. Many of my classes start off this way, gather functionality, and ended up being more than just a enumeration.
That being said...does this pattern work in C++. I would think so but I haven't tried it.
Open Source Identity Management: FreeIPA.org
I'd guess that win32 C++ programmers make up the largest such subset of all C++ programmers. So which C++ community is he talking about exactly?
it can become a performance concern as soon as you start referencing pointers to members of such a class.
Somewhat. The overhead isn't usually that severe anyway, unless you're making calls to a member function pointer in a tight loop, and maybe not even then, depending on the details. However, this is one of the tradeoffs that has to be considered. I use member function pointers primarily to implement closures for building flexible callbacks and tend to avoid them in performance-critical situations in general.
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
In the interview, Scott exhibits amazing ignorance for an author of his supposed stature. As someone else pointed out, use of interfaces has been common in the Win32 world, with COM, for at least about eight years. Smalltalk has used implicit interfaces for decades, and that's where the Objective-C construct presumably came from.
Scott seems to have a very ad-hoc approach to programming - "I do it this way because it seems simpler and it works". That's fine, but if you're going to be writing about this stuff, you'd think you'd perhaps study it a bit, understand the formal underpinnings (type theory in this case), to be able to put your choices into a wider context - but he doesn't do this, unless he's playing dumb in the interview to avoid confusing the readership.
Basically, Scott seems to be a step above authors like Bruce Eckel, who write intro/overview "how to program in language X" books. One shouldn't be looking to him for insight into software design.
Scott seems to have a very ad-hoc approach to programming - "I do it this way because it seems simpler and it works".
My take on his books has always been that they're intended to help novices avoid common pitfalls, rather than to provide insight for experienced developers. When they're considered from that point of view, I think they're valuable books, overall. With respect to a few specific topics, like MI, I disagree with his position even for novices.
As someone else pointed out, use of interfaces has been common in the Win32 world, with COM, for at least about eight years.
Hehe. That strikes me as a rather odd example, since COM is so much newer than all of the other technologies we're discussing, with the sole exception of Java, which was invented at about the same time as COM. But you're certainly correct that this idea isn't new.
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
Ah, I see what you meant. That is a very good example, then. I only used the COM stuff when it was new, and it basically had to be done in C at that point (and was a huge pain). That experience drove me to get a job writing code for embedded systems -- as far away from MS as I could manage...
Of course, the mixin approach was described in the C++ context at least as far back as Booch's OO book.
Really? Wow, I've completely forgotten that. Next time I'm in the office I'll have to grab my copy and see what Booch had to say about it. I've been working at home for two years now, but I still have to cart a box full of stuff home every time I go to the office -- it's been a slow migration :-)
I would expect someone writing books about C++ to be aware of all this, but Scott's comments against MI never seemed to take any of this into account.
I fully agree with this sentiment. Although I think Booch's book came after Scott's first book, an author of C++ texts should definitely be familiar with all of the major literature (and, preferably, most of the minor literature as well).
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
Afaict, Booch's 1st edition ('91) came out the year before Meyer's 1st edition of Effective C++ ('92). Not as big a gap as I remembered - I think I only came across Meyers a few years after that.
My confusion probably arose from the fact that I had Booch's book within a few weeks after it came out and didn't run across Meyer's book until about 1995.
Cool! No-one ever regretted avoiding Microsoft... ;) What kind of embedded systems?
I was working on boards that were pretty close to PCs, in terms of power, Motorola 68000 processors and, I believe 4MB of RAM and 4MB of flash. Initially they were running PSOS and the application (control system for routers of audio and video signals) had just been ported, more or less, to VxWorks when I got there. I've worked on a variety of Unix and embedded systems since and haven't really done any more "serious" Windows programming. And now I'm happily hacking on tiny machines running Linux, even though that means I'm mostly writing C.
It's all fun, actually, but I like to be able to have a little deeper understanding of what's going on and it always seemed that with Windows there was just too much stuff hidden from me. Linux is, of course, ideal in that respect. How much you understand is dependent on how much time you have to invest in learning, not on how much someone will allow you to learn.
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
I can list off 10,000 reasons why C shouldn't be used for much of anything. I have a masters in software engineering from SEI and I can list off dozens of real world fuckups that were caused or allowed because of the C language and costed millions of dollars and at times possibly even risked lives. Then you go out into the world and you will continually stumble into situation when you want portable assembly and there is no better than C. Would I write the code that controls the 767 in C? Not if I didn't have to. Would I write a huge GUI with hundreds of dialogs and controls in C? Not unless you were holding a weapon and threatening me. Now a kernel? A low level library? Something that talks to the metal? I don't know of anything better than C. Ada is nice at times, Forth is wonderful for certain tasks. C is amazing at most of it. You can see the opcodes that get produced as you write it, it's beautiful. Last embedded project I built from scratch we wrote enough assembly to get a CRT0 working and then enough C to get a JVM working and the rest was gravy.
I can wax the same way about gotos. I can point to pieces of code that a absolutely beautiful because they have gotos and couldn't be made as elegent without them. Drivers in the linux kernel use them from time to time. Mind you, most of this stuff is low level but that's what it's for. Tool for the job, you know? And it's beautiful when it works.
In the hands on a newbie it is a little frightening. I think of it like the table saw in shop class. It's the most deadly tool in the room, every shop teacher has personal stories to tell you about kids sawing fingers off and stuff. And without exception there will come a time when that's the tool that does the best job. Sure you could rip a 2x4 in to a 2x2 with a ban saw or even a jig saw but the table saw is the saw intended for that task and it will do it in seconds, perfectly. OOP exists because we think it's easier to model programs in the ways that we think, rather than the ways that computers need them; there is nothing you can build with OOP that you couldn't build with functional or imparitive programming only you might find it easier to do with OOP. So what happens when I can describe something with 2 concrete abstractions? You're saying I should use an abstract abstraction as an inermediate? Take a book, it is a phyiscal collection of bound and printed pages, it is also the collection of words on those pages and in programming land those are both concrete (the specific words are what make the book.) So what if I had a "CollectionOfOrderedWords" object and a "CollectionOfBoundPages" object, my book could be a child of both, specifically myBook might be the mapping between the words and the specific pages. As such I also want to treat it as both. The interface world requires that I actually treat it differently because I want to look at the words rather than the pages. What if I think that abstraction is easier to think about?
MI has it's problems. It's not the safest thing to use but it makes some things very easy. Particularly when you're building complex things rather than decomposing complex things, which seems to be the tendency among a lot of OOP programmers. If you're building a "final" object that won't be a component of anything else, MI is awesome.
I've seen this in the Java world too - people working on top of open source server products are amazingly empowered, compared to those stuck with closed source application servers and tools. I think open source at various levels is a big reason behind the corporate success of Java, but one that's often not recognized.
And one of the nice things about Java, from my point of view, is that it's pretty hard to every *really* hide the source. There's so much information in Java bytecodes that decompilers are highly effective. I've even made custom modifications to supposedly "closed" code -- decompile, hack, recompile. It can also be very interesting to hack a class and rename it, then write your own class with the original class's name and interface that just wraps the original, giving you a chance to inspect and modify everything flowing into or out of objects of the original class. In fact, it occurs to me that you could probably write a little program to automate that -- use introspection to examine the original class and then automatically generate an instrumented wrapper that logs everything... hmmm....
Heck even just using javap to poke around the interfaces of classes that are not intended to be public can often tell you a whole lot about how the software works underneath.
You can do a lot of this stuff with object code, too, but you're disassembling, not decompiling and the difference is huge.
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
The first C++ project (and most after) have involved designing extensibility by using a base class in an executable, and derived classes created in dynamically linked libraries. The point being the executable loads the library, links to a constructor function, calls it and is returned and object B which can be upcast to the base class A. This cannot be done if B is multiply inherited because the executable module does not know about that at compile time.
This has the potential to be a really, really interesting point, but I just don't think it's true. In fact, I'm fairly certain that I have done exactly what you say in the past. Even if my memory is faulty (it commonly is), I can't think of how the dynamic linking changes things. It seems like if this were a problem, it would be a problem with static linkage as well since the units are always compiled separately, and the linker doesn't rewrite the code.
Can you elaborate?
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
I'm replying to myself because after a bit more thought, I can see how you structured things to make it problematic. The key is that you're doing the upcast from B to A in the executable which knows nothing about B (doesn't even know B's name), rather than in the shared library which would know how to perform the upcast properly.
I don't see that as a real limitation, for two reasons: First, there's a very simple fix -- just do the upcast in the library. Second, I'm pretty certain that what you did is not well-defined even with single inheritance. You're effectively doing something like:
... but in a more complex manner. Maybe writing it as:
... is closer to the actuality, but they're equivalent. Now while I think both of the above would "work", assuming single inheritance, in every C++ compiler I've used, I'm pretty certain that the language does not require that it work. That is, compiler writers are permitted to choose a class layout that requires a bit of work for an upcast even in the case of single inheritance, and unless the compiler can recognize the upcast and emit the appropriate code, the results will be... bad.
Regardless of the correctness issue, IMO it's cleaner to do the upcast in the library. I've always taken the additional step of writing a small wrapper that does the actual construction, and then placing that in the library with the class it instantiates. If you needed to be able to get a B as any of its bases (reasonable), you'd need a wrapper per base but, again, that doesn't seem like a terribly painful tradeoff for being able to (a) use MI and (b) be fairly certain that it will always work, even for SI.
If you wanted to be able to use placement new, you'd also need another version of each helper, plus a function to tell the caller how much space to allocate.
Please excuse me if I have completely misunderstood. If there's an issue I'm missing here, I'd very much like to understand it.
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.
Fair enough. Couple of points that I would bounce back at you though:-
The only Good System is a Sound System
Strictly speaking the compiler needs to know the parent and descended type to make this upcast (potentially, even without MI).
Absolutely, and this is the key. Unless you're doing something excessively clever, the compilation unit that performs the upcast will *always* know the derived typed, because, in general, it won't compile otherwise. With the COM example, you are essentially passing it through a cast to void* so that the executable can then convert it to a base* without having to know about the derived type at all.
And that cast via void* is not guaranteed to work, even with single inheritance. As you noticed 8 years ago, the authors of your compilers structured class memory layout in the obvious way and the cast worked. But if it hadn't, the compiler would not have been in error.
If you do this cast without knowing what the descended type really is, you are taking a pointer and just lying to the compiler what the memory print looks like.
Yep, and lying to the compiler is a dangerous business. It can be fun, though ;-)
The reason this isn't a problem, usually, with static libraries is merely that the compiler usually knows the descended type, and inserts code to do the conversion.
More precisely, the reason it isn't a problem with static libraries is that there's rarely, if ever, any good reason to pass a cast through a void*. In the case of dlopen/dlsym (or the Win32 equivalents, whose names I forget), you're constrained by this 3rd party API, and when you pass the pointer through that bottleneck, you lose knowledge of the derived type. Forgetting the derived type is cool, but you need to upcast before forgetting, not after.
I think in a multiparadigmed language it's inevitable that you will have to understand not only C++ but the C++ philosophy of a particular project.
This is absolutely true.
Playing games at the level of the binary layout of your strucutures is one of the things that makes people now say that C++ is too low level.
As you said, it's not the language's fault if you abuse it. The ability to do low-level work is important for a large class of programming problems, and the fact that you can (ab)use the same pointer manipulation tools that are needed for, say, device drivers, to subvert the higher-level type system is just an artifact of the language's breadth of applicability. It's up to the programmer to avoid doing silly things -- and the compiler will generally tell you if you are! I think it's adequate that C++ makes nasty, tricky code obviously nasty and tricky. It doesn't need to disallow it.
By way of comparison, I've been writing Javacard applets recently and it is a real pain in the neck. When you're writing code for an 8-bit microcontroller with 2K of RAM and 16K of storage, you *must* write code that is jealous of every byte. Doing this in Java is like trying to swordfight while hog-tied. I like Java well enough, but it really sucks for low-level work. C++ can do a wide range of jobs effectively; just don't blow your own leg off with it.
Note to ACs: I usually delete AC replies without reading them. If you want to talk to me, log in.