Letters

Dr. Dobb's Journal February 1998


Asserting Yourself

Dear DDJ,

"Improve Your Programming with Asserts," by Bruce D. Rosenblum (DDJ, December 1997) on assertions was very interesting. Here's my favorite assert:

#define ASSERT(x) 
  (if(x);else{* ((void **)0) = 0;}

The beauty of this is that the debugger kicks in and points right to the line where it failed. You can place in-line comments here to describe what went wrong (plus, you can see the argument to the assert). Also, you can invoke the call stack and all that good stuff.

Of course, it only works in operating systems (like Windows 95/NT) that protect that region of memory.

Jason Feingold
jfeingold@gamry.com

Dear DDJ,

Regarding Bruce D. Rosenblum's December 1997 article: A function or procedure can be thought of as having a contractual agreement between the procedure and the caller.

The function will perform work generating some type of result. The function will guarantee its results will fall within a valid range, provided the function is supplied information within a valid range.

All values that enter and leave the function should be asserted that they meet their "contractual" agreements.

Also, one improvement to the assert(bool,char *txt) function call could be to add a vararg component.

assert((x>0) && (x<32),"X out of range,value=%d ",x);

Assert now outputs like printf, thus providing more information to the programmer.

David Preisler
dpreisler@mktdata.com

Ada Fan

Dear DDJ,

I was thrilled to see Gavin Smyth's article "GNAT: The GNU New York University ADA Translator" (DDJ, December 1997). I would like to see more coverage of the Ada language, particularly the new Ada 95 language. Ada came of age many years ago and is used throughout the world in a variety of applications. I've been involved in developing everything from database front-ends, communications systems, and missile systems -- and all written in Ada. Once again, thanks for Gavin's article.

Kenneth Price
KJPrice@bellatlantic.net

So What's Wrong with C++?

Dear DDJ,

In his article "Advanced Object-Oriented Features for C/C++" (DDJ, August 1997), Blake McBride states, "Private members are not entirely private, since they are declared in a publicly accessible header file." This need not be so. The well-documented Proxy pattern has long been used to solve this particular problem (and popular misconception). In the proxy class declaration a pointer to the actual object being worked upon is all that is required to appear in the header file, and since a pointer may be forward declared without inclusion of the appropriate class header file, a dependency is eliminated. Since, also, that implementation is now totally unpublished, it is even more opaque than the generic pointer used in Dynace. It is only necessary to include the class declaration for the implementing class in the cxx/cpp file for the proxy methods. I contest that it is necessary for header files, since unpublished interfaces tend to result in nonrobust designs, and for this very same reason, I consider run-time binding of method names to be potentially dangerous. If a published interface changes, then all dependent files must always be recompiled. Fortunately, published interfaces change infrequently by comparison to changes of internal implementation and private members, so this still satisfies a requirement to withstand heavy changes. Blake's assertion about dependencies and rebuilds in C++ is simply irrelevant when the proxy pattern is used, since only the proxy's methods require recompilation even if private members and the size of the implementing object is changed. Users of the proxy are totally unaffected, aside from re-linking, as long as the interface remains stable. A problem of many proxies is that constructors and destructors are usually responsible for allocation issues, though since Dynace, too, has run-time overhead this is not a disadvantage. Indeed in some cases it can be used to benefit.

Regarding pointer problems, sometimes this can be resolved by passing references to objects instead of pointers. It is more difficult to get invalid references unless you deliberately contrive to do so. Lazy initialization though a proxy can also help here.

Inheritance hierarchies hidden behind the proxy interface can be duplicated publicly, though I often find that C++ beginners have an over enthusiasm for inheritance. The secret is not only to know when to use it, but also when not to use it. It should be used to achieve polymorphism; a symptom for this requirement is big switch statements or modal parameters. It should not be used to duplicate functionality for apparently unrelated objects unless indeed there is some relationship that can be described in a common parent class. A common design problem is to use too many levels of inheritance; two or three are the most you are likely to see in any good design. The temptation to overgeneralize with many layers of inheritance should be avoided at all costs. Designs are more likely to be reusable where class interfaces and dependencies are minimized. If a class definition describes unrelated aspects or modes, then it should probably be separated into distinct classes.

As for the benefits of classes defined at run time, any OMT Design Pattern proponent will remind Blake of the Composite pattern. This can be well used to provide metaclasses in C++. In my opinion, the effort required for implementing Dynace (from what I know from Blake's article) would have been far better utilized if he had decided to work with C++ to accomplish this instead of against it. Having said that, use of metaclasses, too, is often unnecessary, since many problems can be described adequately with very simple objects. Strong typing (at compile time) leads to more efficient solutions.

Another general note: I consider that it is a lack of understanding of well-known design patterns in conjunction with poor (hacking) design that leads to a bad reputation for C++ amongst hardcore C programmers. C++ offers features that make it much more powerful, but the solution is to have a thorough design. In my experience, so many projects are continuing to use C++ compilers for C-only functionality (and not gaining the benefits that several good design techniques can bring), simply because not enough people seem to know what can be accomplished if you abandon the old practice of coding before thinking.

My advice, drop the misconception that C++ is automatically object oriented: That is the responsibility of the designers of your system (perhaps you). I agree that poor design can lead to clumsy solutions in C++, as in any language, but the power of C++ in conjunction with established techniques such as the use of Design Patterns with Rumbaugh OMT/Jacobsen Use Case can lead to good designs that are portable. Avoid over-reliance on platform-specific class libraries (some of these suffer from design oversights mentioned above), or use an adapter class with them where you require portability (if you're prepared to provide equivalent support for other platforms). Focus on the structure of the broader problem, not on implementation issues. Even if there is no commercial C++ compiler for your platform of choice, C++ is still probably supported with GNU g++, so I really don't see the need for "Yet Another Object Oriented-Language," since YAOOL is not going to solve poor design.

Iain W. Bird
iwb@birdsoft.demon.co.uk

Blake responds: I'm familiar with the proxy pattern trick Iain describes. Since Dynace tricks C into being an object-oriented environment, obviously, similar tricks can be done in C++ (actually Dynace works in C++ too). When writing an application the point is to spend as much effort solving a particular problem and as little time with extraneous coding (unrelated to the problem). The proxy code described requires the programmer to implement it in every class he uses, whereas Dynace has already done all the work for you. With Dynace, you spend zero time implementing tricks to get around a fundamental problem with the language. On the other hand, using native C++ you'd either have to live with its shortcomings or constantly write code to eliminate its shortcomings.

Iain's comments about published interfaces regarding my article had limited scope so you wouldn't know that Dynace automatically generates header file(s) which contains class protocols. Dynace also supports full (and optional) compile time argument (protocol) checking. The relationship between header file changes and recompilation is the subject of another article I have written but never published. It only relates to Dynace and I'd be happy to e-mail the article upon request.

The problem with run-time binding has never been any kind of "danger." Run-time binding has always been considered more powerful, expressive, and flexible than compile time binding. The problem has been efficiency and Dynace solves that in a very practical manner.

Again, Iain's suggested "tricks" to get around C++'s problem with pointers is already implemented by Dynace. Why keep writing the same code to get around significant problems when you can use Dynace, which already solves them with no extra trick coding needed.

With regard to Iain's comments about run-time class definition and metaclasses, I agree that in practice they are rarely directly used. On the other hand, one need only look at books like The Art of the Metaobject Protocol or related work at the Xerox Palo Alto Research Center to see the extreme benefits of this approach.

True, strong typing has the benefit of being very efficient in terms of CPU at run time. On the other hand, weak typing is much more flexible, expressive, and produces much smaller code, all at the cost of a small and reasonable run-time hit. Dynace includes techniques which can reduce this run-time cost to below C++ in tight loops.

In response to Iain's general comments about C++, it seems to me that C++ has become the Ada of the '90s. C++ has taken a small and straightforward language (C), which had a relatively small learning curve, and created a language that has so much complexity and syntactical nuance that it takes years to learn and master. Instead of spending time solving problems most programmers are spending all their time learning C++ and MFC. Code written by expert C++ programmers can't be deciphered by anyone with less experience. Learning C++, debugging, and compiling time take up a programmer's entire life with little time to solve real problems. Dynace attempts to solve those issues.

DDJ


Copyright © 1998, Dr. Dobb's Journal