
- Home
- Magazine
- Conference & Seminars
- News
- Archives
- Forums
- Store
- Directory
- Editorial
- Advertising
- User/Login
- Contact



This year's WWDC was by most accounts pretty shy of substance. Last year we had System 7 (at last) and QuickTime, for example, while this year we were told that AppleScript would go beta sometime in the fall; a cross-platform MacApp strategy would be announced a month later at PC Expo; sometime in 1993 QuickDraw GX would hit the streets, sometime; somewhere Taligent will need us . . . in short, lots of promises but not much delivered. It was against this backdrop that BAMADA held its monthly meeting on Thursday night of that week. Despite holding the meeting off-site in Cupertino, rather than in San Jose near the convention, the auditorium was packed to overflowing, and it is a good bet that almost everyone there came to hear Steve Weyl expand on Apple's cross-platform strategy or Larry Tesler talk about Dylan, Apple's new object-oriented dynamic language (OODL). Also on the program were two tiny companies, Component Software demonstrating their Component Workshop development environment and Nick Nallick demonstrating his view editor, Ad Lib. Sandwiched in between the heavyweights, the little guys stole the show. Looking around after Component Software's presentation, it was clear to me that only a vague sense of protocol prevented a standing ovation. This, from a company that has yet to ship a product and has only nine employees.
This article was prompted by the audience response to Component Workshop and, more generally, the sudden upsurge of interest in OODLs following Larry Tesler's keynote talk at the MADA conference in February. In the next issue of FrameWorks I will provide an in-depth review of the tools and class library of Component Workshop. This article is devoted to the particular implementation of C++ in Component Workshop and even more to the issues and position of this kind of tool in the world of OODLs.
It is easy to get swept away at this point and equate OODLs to faster compiling and linking. That is certainly attractive and probably in itself explains much of the current interest in OODLs: it is not the language we want, just faster compile-link turnaround. But that is only scratching the surface of the power of an OODL: The entire paradigm of the development environment is fundamentally different. I was first captivated by Macintosh Common Lisp three years ago when Andrew Shallit of Apple demonstrated it for me at Boston MacWorld. He started developing his application by simply creating some objects and adding a menu bar to the set of menu bars available in the environment. When he needed an Edit menu, he switched menu bars to the development environment's menu bar, copied the Edit menu to the clipboard, switched back to his application, pasted it in and it worked immediately! Think about this for a moment: it is almost as if you started from the complete development tool suite, then tacked your own classes on top. Anything used to create the development environment itself is also available from the outset for use in your own applications. In most OODLs, this even includes the compiler, so you can write programs that read text from some source and invoke the compiler at run-time to extend the program.
The other difference concerns the way you do development. The following might be a typical way to start creating an application using an OODL.
This is a far cry from the current MacApp environment, in which we have development tools on the left and running apps on the right and never the twain shall meet. You have to perform elaborate rituals to get back and forth: compiling, linking, launching, shutting down, repeat. To me, this is the single most striking difference between development with OODLs and development with statically compiled languages like C++.
Another consideration is garbage collection. Dynamic languages have them, statically compiled languages like C++ as a rule do not. It has been estimated that as much as 50% of a C++ programmer's time is spent tracking down dangling pointers, double deletions of the same object, memory leaks when objects are not deleted before their addresses are thrown away, and so forth. Lisp, Smalltalk, Prolog, Scheme, and most other dynamic languages do away with this problem altogether by turning memory management over to the underlying environment. When an object is no longer pointed to by anyone, it is automatically deleted.
So far, we've talked about dynamic languages in general, and this same discussion, with a suitable replacement of terminology, might have applied to non-object-oriented dynamic language environments as well. Object-oriented dynamic languages typically add some additional features that preserve the object paradigm at run-time, rather than just compile it out of existence, as happens with C++ and to a large degree Object Pascal and others. Smalltalk and CLOS, to name two such languages, provide run-time dispatch of methods based on the actual type of an object. This is a deep subject that we'll return to later in this article; I mention it now to set up the apparent paradox of Component Software: C++ is generally hostile to dynamic features, yet Component Workshop is a true OODL that uses C++ syntax and semantics. This, more than anything else, is the intriguing innovation of Component Workshop.
The truth traditionally has been this:
Most of the research that has gone into OODLs over the years has been directed at the first of these problems and I am happy to say slow performance in OODLs is now largely a myth, at least for the major-league products. The second, fourth and fifth issues are also being worked on feverishly, but don't seem to get the mindshare of the engineers working on OODLs, most of whom, I have found, wonder why anyone would be bothered by a 2.5M memory partition for the Nothing sample. The technology for addressing these issues is well-understood, however, and it is only a matter of time before they enter the mainstream. In particular, a technique called generational garbage collection tosses out a little garbage all the time, rather than waiting for it to pile up and overflow onto your kitchen floor.
However, if these get short shrift, the down-to-earth marketing consideration that people don't like all those parentheses and brackets seems altogether lost on the people who can do something about it. Tesler made the point in his keynote talk that Lisp and the like can be made to look like any other, "normal" syntax if that is what you want. It is, in fact, a standard undergraduate Lisp project to add this sort of syntactic sugar, to turn
(+ 3 4 5)
into
3 + 4 + 5
or
(defun f (a b c) (+ a b c))
into
f(a, b, c)
{ return a + b + c; }
It is easy, but never seems to be taken seriously. Also, this translation is at best a crude approximation, akin to translating poetry from one language to another by using a multilingual dictionary one word at a time without regard to overall texture and meaning. The second of these examples only looks like C; it isn't really because C has different semantics than Lisp that Lisp can only approximate without an inordinate amount of effort. You still have to learn a new language, even if it looks a lot like your old one.
Let's face it: what people really want is C++ without the headaches and broken hearts. Beyond a small but zealous band of Lisp and Smalltalk fanatics willing to overlook the problems (myself among them, I should add in all fairness), C++ has already won the war without even having to fire a shot. Perhaps Dylan or other languages can achieve widespread acceptance in the next generation of programmers, but C++ is now the Cobol of the 90s: even if you don't like it, even if it isn't the best language possible or even available, that's where the money is. So that leaves us not questioning whether C++ is the dominant programming language but rather how to best write in C++. And the first place to start is with possible cross-fertilization from the best of what OODLs have to offer. But on the surface this looks almost hopeless.
C++ can arguably be called an object-oriented translator, rather than an object-oriented language. This is regardless of whether you use a native compiler or one that produces C code as its output. There is very little object orientation left once the compiler is through. You can't, for example, look at a non-PascalObject instance in memory at run time and tell (unless you are a Steve Jasik willing to crawl about through RAM reading nuances into memory addresses of vtables) what class it is an instance of, what the class hierarchy above the concrete class looks like, or even that classes ever existed. Operator overloads are all resolved at compile time, which means that the actual type of the instance isn't used, only the type of pointer that existed at the spot in your code where you used the operator. Typecasting in C++, on the surface so elegant through user-defined conversions, in practice degenerates into a schoolyard baseball game. The skinny rich kid, the C++ compiler, isn't picked for a team until last and, in a fit of pique, picks up his ball and bat and goes home. If you do anything with constructors, assignment operators, or typecasting, C++ leaves in a huff.
Furthermore, there are mechanical, implementation details that defy any rational attempts at garbage collection. If you use multiple inheritance and typecast from base class to derived or vice versa, C++ actually pulls the slimy trick of moving the pointer to the object to a different offset. (This, by the way, is why handle-based objects can't use multiple inheritance in C++: the old pointer-into-the-middle-of-a-handle problem.) Try to implement any sort of garbage collection and compaction with that as a starting point. It can be done, but one can admire a ten foot high scale model of the Statue of Liberty made of used bottlecaps while at the same time wondering why the artist chose that particular medium.
I haven't even gotten warmed up on the list of reasons why C++ simply can't be an OODL, now or ever. Unfortunately, no one told Component Software in time that it can't be done. Now it's too late, because they've done it. For all of us who expect to earn most of our money with C++ while pining for a real OODL, it is time to sit up and take notice.
The development cycle I talked about above, creating and testing as you go, is fully implemented in Component Workshop. You create C++ classes on the fly in the time it takes to read this sentence. Same for methods. Even though that was a pretty short sentence. And they aren't just immediately checked for syntax errors; they are immediately compiled, linked and ready to respond to messages for all instances of that class. Rather than compile the entire program in order to run it on a carefully prepared test bed, you simply create instances as you program and test methods by firing them off for the instances you have created. Add a new method to a class and all instances of that class in your environment instantly respond to the new method. Same for changes to method implementations.
In your mental checklist for OODL features, start now by checking off "dynamic environment."
Next up is garbage collection. Imagine never again having to explicitly call "delete aGarbage" again. When an object is no longer referenced, it is simply tossed out with the old lettuce, automatically. And Component Workshop's garbage collection is of the generational sort we talked about earlier, always being done silently in the background without having to interrupt workflow with a steaming coffee cup cursor. Toss in a compaction scheme similar to the Macintosh Memory Manager handle strategy, but independent of the MMM, that reduces memory fragmentation. This is all implemented in Component Workshop.
So check off "garbage collection."
Let's talk efficiency. In theory, their dispatching of messages should be no worse than any other OODL and usually as good as C++. (I say in theory, because I have not yet been able to run benchmarks, although we have gotten down and dirty in discussions of how it is implemented.) In some cases, they should be able to do better than C++ in the final, extruded code, since they can perform global optimizations, while C++ optimizes method-at-a-time. For example, one classic problem with C++ is that it is difficult to optimize a monomorphic method out of vtable dispatching. This is because the vtable is set up locally, while to be sure that it is never overridden you have to look at the entire program. Object Pascal does this only with the cooperation of the linker and at the cost of slower dispatching everywhere else. Overall, Component Workshop's dispatching should be comparable to or better than pure C++.
Check off "fast."
Now for space. Component Workshop is like other OODLs in that while developing you have to carry around not just your application, but the entire development environment as well. Having gotten used to setting my MPW partition to 8M to allow the linker to limp to the finish line carrying MacApp on its back, that doesn't really bother me. Component Workshop, in fact, runs in a much, much smaller partition on a IIci and still has time to stop for a nap along the way. But that's never been the real concern with OODLs. It is the size of the finished, shippable app that is typically huge. With MCL, 2.5M is a reasonable starting point, although Apple is working hard to get that down to a trim half meg or so. Component Workshop has a feature called extrusion that, again on paper, does much better. Their claim is that a minimal app like a simple text editor should be about 150K. Now we're talking. Extrusion involves stripping unneeded stuff like the compiler and only spitting out the essential code needed to actually run the application. Right now the code that comes out is ANSI C, which is then compiled using your favorite neighorhood C compiler to produce the finished application. I have not yet used the extruder, so I can't say for sure whether it is as good as they claim, but again, based on talks with the folks at Component Software I, for one, think they aren't just blowing smoke.
Next to "small," write "check your September Frameworks for Jeff's test drive."
Component Software dispatches methods based on the type of the instance, not the type of the pointer. This means true, run-time object-oriented dispatch as one would expect in a conventional OODL. It also means changing some of the semantics of C++, a subject I'll return to in a minute, but in my opinion for the better.
Next to "true object-oriented code" put a big bold checkmark.
That's about the end of my OODL checklist and Component Workshop passes with flying colors.
There are three areas of deviation from the language standard:
As an example of the first, consider this arcane syntax in C++:
TClass::AMethod (args) { implementation }
Suppose you could graphically imply the class, rather than having to state it directly? Specifically, suppose that the window in which you are editing the code has the class name in its title? Why would you want to have to type TClass:: just for the sake of form, since neither the compiler, the linker or the user need it? There are several such "features" of the C++ language standard that are really not part of the semantics of the language, but rather conveniences for compilers and linkers. Component Workshop rather sensibly does away with these. There aren't many; they do not change any of the semantics of C++; and they aren't burdensome.
As to the second list, there are a lot of those right now, unfortunately. For example, the initially shipping version will not support #define macros or any other preprocessing, operator overloading, or templates. They all wreak havoc with incremental compilation and, though generally useful, have a marginal enough value that Component Software has chosen to ship a useful version to earn some money while working to shore up the weak spots. I have been told that becoming fully compliant with C++ v3.1 is a high priority, but we will have to wait and see. I suspect that CS themselves do not yet know just how strong the demand will be for some of the more arcane features of the language (did you know that you can declare classes inside classes or even inside statement blocks?) once people get used to what they can do without them.
The third list is the one likeliest to cause controversy. To some extent, Component Software has decided to "fix" C++. They have borrowed some concepts from other languages like CLOS and Eiffel and imported those ideas into C++. These in some cases extend the language in non-standard ways. In other cases, they have retained the original syntax but changed the semantics. From a purist's viewpoint, the changes all seem to make good sense. For example, CLOS has the concept of linearizing inheritance. Rather than throw its hands up at inheriting the same function name twice, as C++ does, CLOS has a well-defined search order that follows the order of what in C++ would be the derivation list. This allows the use of the keyword inherited even in otherwise ambiguous situations. Component Workshop has imported this concept from CLOS. You can use inherited and otherwise expect CW to do the right thing based on the order in which you set up the derivation. This also allows much, much cleaner implementation of multiple inheritance. Good idea, but goes against the grain of one of the design philosophies of C++: faced with even slight ambiguity, pick up your ball and bat and go home.
Another example: virtual data members. You say there's no such thing? There is now! In Eiffel, you can't tell whether a data member is an actual piece of data or a computation, because the name of the data member is hidden behind accessor (Get/Set) functions of the same name. This idea has been imported into Component Workshop in the form of a virtual data member. Virtual data members allow abstract classes to access a data member as if it is a simple piece of data, all the while deferring the decision of how to actually implement that data member until you create a concrete class. If you want to retain accessor functions, you can do so transparently. If you want the efficiency of direct access to the data members, you can do that just as easily. This is a sensible notion, and it is one of the keys to efficient implementation in Component Workshop, but it isn't C++ semantics.
Another example: all member functions are virtual. You can't decide otherwise. This, too, makes sense in this environment. The keyword virtual only exists in C++ so that the compiler can optimize the calls. It is a programmer's minefield and, absent efficiency considerations, you wouldn't want to even consider using non-virtual member functions. In Component Workshop, the extruder can tell which functions should be treated as virtual and which should not, so the need for non-virtual member functions disappears. Again, however, this breaks off a small corner of C++ semantics.
Another example: all derivation is virtual. In C++, virtual base classes must be explicitly set up by the programmer, otherwise multiple copies of a base class are set up inside each instance. Multiply inheriting the same class is one of the most questionable design practices in C++, anyway, and the only reason for not using virtual base classes is that C++ is pretty dumb about how it implements them. You can't typecast to a virtual base class then typecast back down to a derived class, to name only one annoying "feature." In Component Workshop, the equivalent to C++ virtual base classes is implemented quite cleanly and without taking away the ability to typecast downward. Once again, great idea, but a deviation from standard semantics.
Any of these taken individually would make a lot of sense. Taken together, they start to push the limits for anyone who has a purchasing checklist that insists on full C++ compatibility. Putting it bluntly, the people most likely to appreciate these improvements are the ones most likely to be happy with Smalltalk or Lisp in the first place. Component Workshop will have to come up with a strong case to overcome bureaucratic obstacles on this one, even though their changes, in my opinion, really are improvements.
class Foo {
// class declaration here
};
class PFoo {
private:
Foo* fFoo;
public:
// Constructors & etc.
Foo* operator -> (void)
{ return fFoo; }
Foo& operator . (void)
{ return *fFoo; }
};
// In the program somewhere
f(PFoo pf)
{ pf->DoSomething(); }
This is a crude approximation to the way one would actually use this paradigm, but the basic trick is to treat a pointer to an object as itself an object. PFoo has been called various things in the literature: Dewhurst and Stark (C++, Prentice Hall, 1989) called them "smart pointers," while more recently James Coplien (Advanced C++ Styles and Idioms, Addison Wesley, 1992) has called the PFoo classes "envelopes" and the Foo classes they point to "letters." I will use Coplien's terms, since that is what Component Software has chosen as well.
The advantages of always accessing through envelopes are so compelling that it is becoming a quasi-standard design strategy for those who really know the language. One can easily, for example, test for null pointers and recover gracefully, either through a failure or by returning some sort of default object when the pointer is null. If strict discipline is used, it is possible to implement lots of other very advanced memory management strategies within this framework:
And this is just the tip of the iceberg. The reason I am belaboring this idiom is that it is the single most critical key to the memory management strategy of Component Workshop and, therefore, something that a programmer in that environment must be comfortable with. CW does a great job of hiding all the gory details from you, but there are some curious semantics that you have to get used to. For example, in CW when you declare a variable like this
MyClass anInstance;
you haven't declared an instance, just an envelope. You separately must create the instance pointed to using the next design idiom, factory objects. Again, I want to emphasize that these concepts are considered by experts to be sound design practice for anyone using C++, but your run-of-the-mill C++ programmer hasn't heard of them.
The concept of a class in C++ is pretty amorphic: it can mean anything you want it to mean. We have already seen an example of this: the same basic syntax applies equally to envelopes and letters, even though their semantics are quite different. In other object-oriented environments, notably Smalltalk and Eiffel, conventions for categorizing classes have arisen out of the necessity to make sense of big, complicated programs. Some of this has been imported into the world of C++ by Component Software.
Classes in Component Workshop are one of three types:
Factory classes are familiar to anyone who has worked with any metaobject protocol (CLOS) or class object (Smalltalk) environments, but do not have any direct counterpart in pure C++. Here is perhaps the largest single deviation from standard C++ in Component Workshop: in place of operator new and constructors, you have methods of the factory class.
MyClass anInstance = MyClass->new;
This is the way one would create a new instance of MyClass, but the syntax does not quite make plain what is really happening here. On the left-hand side of the equal sign we are declaring a new envelope; on the right-hand side we are sending a message to the factory object for MyClass, asking it to create an instance- the letter - and return its address. anInstance then points to that instance. Constructors are not called as a result of this. Instead, you are free to implement whatever methods of the factory class that you like. In this case we used new, but there can be any number of methods that create and initialize instances and some or all can take arguments. For example, we could use the following instead:
MyClass anInstance = MyClass->MakeBigOne(<arglist>);
where <arglist> is some set of arguments according to your needs. You implement factory methods like methods of any other class, so the set of "constructor" methods of the factory for a given class is up to you.
The syntax of this is close to that of pure C++, but not exactly the same. The semantics also compare closely to those of pure C++, including default "constructors" used in the event you don't specify which one to use, but the whole idea of a factory class is new.
One can argue whether this is a good or bad idea from a design point of view, but it clearly isn't standard C++. It appears to me that there is no technical reason why Component Workshop could not simply implement the same ideas within the standard C++ operator new/constructor syntax, leaving fewer buying objections and closer conformance to training books and classes in its wake, but neither is the current approach particularly difficult to learn or teach.
Destructors are for the most part unnecessary with good garbage collection in place. If you need to do special processing upon deletion of an object, the object receives a message when the garbage collector is about to toss it. Again, this is a non-standard treatment but one that is consistent with the overall semantics of C++. And again, there appears to be no good reason why the death notification could not have been implemented using C++ destructor syntax.
As to the distinction between abstract and concrete classes, this, too, represents good design practice with or without Component Workshop. In Component Workshop, concrete classes do not add additional member functions or data members, they only specify implementations. It is not burdensome and should probably be done in any program for reasons of modularity and code reuse, but it is an unfamiliar design strategy for most C++ programmers. Component Workshop takes specific advantage of the knowledge that concrete classes do not add to their inherited interfaces to perform some very, very clever optimizations in the way the instance is actually represented in memory, which is much of the reason it ends up so fast and so flexible. changes/limitations. One would hope so, but we're talking about bureaucracies here.
For everyone else, particularly the MacApp community, I think this is a breakthrough product. Despite some of the deviations from the C++ standard, it is still at least 90% standard C++ and many of the deviations simply drop stuff you don't want to do, anyway, in an environment without source files. It is a lot easier to learn this than, say, Lisp or Smalltalk, if you already know C, C++, or Object Pascal. This should translate into an easier time staffing, managing and maintaining projects. Component Workshop also makes it a lot easier to get the OODL you want while justifying to your boss or client that "it's really C++, it's just packaged a little different. Don't worry about it."
I also am waiting to see the class library. The technology of OODLs is now well-advanced and their biggest innovation is not in the area of OODLs, but in how to turn C++ into one. Class libraries, however, are still very much in their infancy and there are few guideposts on the path to making a great one. Today, an OOP language, no matter how great, can be no more powerful than the surrounding tools and class library. There is reason to be hopeful and so far the technical track record is pretty good, so let's see what it all looks like in a few months. n
Nancy Benovich is the product manager and, in a refreshing departure from marketing tradition, also holds the title "Senior Developer."
She can be reached at (617)862-9700.
Component Software is located at
420 Bedford Street
Lexington, Massachusetts 02173




