I spoke last week at the Borland International Conference in Orlando, Florida. The conference was held on Disney property in the Swan and Dolphin resort hotels. In true Philippe style, the parties were the highlight, and there were several conference-hosted trips to the Disney theme parks. The vision of Borland's David Intersimone dressed like an Egyptian and getting shot by a bad guy in the Indiana Jones attraction is something that we'll not soon forget.
Several sessions at the conference addressed patterns, a newly evolving design approach being studied by a small group of industry collaborators. Kent Beck, one of the group, has written about patterns in DDJ and elsewhere; see, for instance, "Patterns and Software Development" (DDJ, February 1994). Jim Coplien and Grady Booch are also members of the patterns group, which is attempting to define how designers can identify and reuse patterns that repeat themselves in the expression and solution of software-design problems. Correlations exist between patterns in architecture, which is centuries old, and patterns in software design, which is only decades old. A pattern is an expression of both the problem and its solution. This is not a new idea. In the early 1970s, we taught that the solution to a problem should resemble the problem; that you should be able to derive the problem by looking at the solution. Patterns in solutions are things that we recognize from experience, and they should clearly express their purpose. Putting this recognition to advantage involves recording the nature of patterns and identifying where they apply in the expression of other problems and solutions. Expect to see a lot more about patterns in the next couple of years. Jim Coplien expresses concerns that zealous book writers will rush to publish and eager toolmakers will crank out early CASE tools well before anyone clearly understands patterns. His advice: Try this at home, not at work.
The ANSI/ISO X3J16 committee's standard definition for C++ includes extensions to the language that the C++ programming community at large wants and needs. The principal new features--those not supported by all current C++ implementations--are templates, exception handling, namespaces, new-style casts, and run-time type information.
Although several contemporary compilers support templates, the definition is changing significantly. The view of a template as a kind of macro is gone. In some implementations, the template member functions must be in view during the instantiation of a parameterized type object. The template header file has not only the template class declaration but must have the member-function definitions, too. The new language definition provides for the member functions to be in their own translation unit, unseen by the code that uses the template. The binding of unique parameterized functions to types is done by the linker, rather than during the compile, to suppress code generation for multiple uses of the same template for the same type in different translation units. The former is the preferred model, according to Bjarne Stroustrup in The Design and Evolution of C++ (Addison-Wesley, 1994). Not all existing compilers work this way, and some will have to change.
Several compilers now support exception handling. Its behavior is well understood, and the design is nailed down. The efficiency with which compilers will implement exception handling remains to be seen. I discussed the subject in two successive columns last year.
I have not yet experimented with namespaces. They have not been implemented in the PC compilers that I have. The feature is designed to eliminate a problem that occurs when multiple libraries in a project use colliding global identifiers. The namespace feature places independent global declarations within unique namespaces to isolate them from one another. Tom Pennello, vice-president of engineering for MetaWare (developers of a compiler which does support namespaces) wrote on the topic last month; see "C++ Namespaces" (DDJ, August 1994).
New-style casts are replacing traditional C and C++ typecast notation, providing safer notation that can reflect the design of polymorphic class hierarchies and that can be readily located in code with text-searching tools such as grep. The old-style casts are still supported by the language, but their use is discouraged and they will gradually disappear as new programs replace old ones. In a perfect universe, programs need no casts at all, and the framers of the language would like to have eliminated them altogether. Research shows, however, that many idioms require them, particularly in systems programming. The old-style cast is known to be unsafe, error prone, and difficult to spot when we read programs. The new-style casts are an attempt to improve the casting situation.
There are four new casting operators. Each one returns an object converted according to the rules of the operator. They use the syntax cast_operator <type> (object), where the cast_operator is either dynamic_cast, static_cast, reinterpret_cast, or const_cast; the type argument is the type being cast to; and the object argument is the object being cast from.
The dynamic_cast operator casts a base-class reference or pointer to a derived-class reference or pointer. You can use it only when the base class has virtual functions. It provides a way to determine at run time if a base-class reference or pointer refers to an object of a specified derived class or to an object of a class derived from the specified class. Example 1 shows how you use it. If you use references rather than pointers, dynamic_cast throws a bad_cast exception when the target is not of the specified class.
The dynamic_cast operator provides a form of run-time type identification (not to be confused with run-time type information, RTTI, discussed shortly). A program can determine at run time which of several known, derived types a base-class reference or pointer refers to. This feature supports idioms that virtual functions might not. The Control class in Example 1 knows that a derived EditBox object has unique requirements not shared by all derived Control objects. Rather than burden all classes derived from Control with empty virtual functions to emulate those unique to EditBox, the design casts the object's base pointer to point to an EditBox object. If the object is not an EditBox or of a class derived from EditBox, the cast returns a 0 value, and the program knows not to call functions unique to EditBoxes.
Unlike dynamic_cast, the static_cast operator makes no run-time check and is not restricted to base and derived classes in the same polymorphic class hierarchy.
If you are casting from a base to a derived type (not always a safe conversion) static_cast assumes that its argument is an object of (or pointer or reference to an object of) the base class within an object of the derived class. The cast can result in a different, possibly invalid address. In Example 2, if the bp pointer does, in fact, point to an object of type C, the cast works correctly. If it points to an object of type B, the cast makes the conversion, but the effective address is less than the address of the B object with the difference representing the size of the B class. This address is incorrect.
Similarly, if the pointer points to an object of the base class, using the derived class pointer to dereference members of the nonexisting, derived class object causes unpredictable behavior.
If you are unsure about the safety of the cast, use dynamic_cast and check the result.
If you are casting from a derived to a base type (a safe conversion), static_cast assumes that its argument is a valid object of the derived class or a pointer or reference to an object of the derived class.
You can also use static_cast to invoke implicit conversions between types that are not in the same hierarchy. Type checking is static. That is, the compiler checks to ensure that the conversion is valid. Assuming that you did not subvert the type system with an old-style cast or reinterpret_cast to coerce an invalid address into a pointer or initialize a pointer with 0, static_cast is a reasonably safe typecasting mechanism.
The reinterpret_cast operator replaces most other uses of the old-style cast except those where you are casting away "const-ness." It will convert pointers to other pointer types, numbers to pointers, and pointers to numbers. You should know what you are doing when you use reinterpret_cast just as you should when you use old-style casts. That is not to say that you should never use reinterpret_cast. There are times when nothing else will do.
The three cast operators just discussed respect const-ness. That is, you cannot use them to cast away the const-ness of an object. For that, use the const_cast operator. Its type argument must match the type of the object argument except for the const and volatile keywords.
When would you want to cast away const-ness? Class designs should take into consideration the user who declares a const object of the type. They do that by declaring as const any member functions that do not modify any of the object's data-member values. Those functions are accessible through const objects. Other functions are not. Some classes, however, have data members that contribute to the management rather than the purpose of the objects. They manipulate hidden data that the user is unconcerned about, and they must do so for all objects, regardless of const-ness.
For example, suppose there is a global counter that represents some number of actions taken against an object of the class, const or otherwise. In Example 3, if the declaration of the A::report() member function was not const, the using program could not use the function for const objects of the class. The function itself needs to increment the rptct data member, which it normally could not do from a const member function. const functions cannot change data values. To cast away the const-ness of the object for that one operation, the function uses the const_cast operator to cast the this pointer to a pointer to a non-const object of the class.
The typeid operator supports the new run-time type-information feature. Given an expression or a type as an argument, the operator returns a reference to a system-maintained object of type Type_info, which identifies the type of the argument. There are only a few things that you can do with the Type_info object reference. You can compare it to another Type_info object for equality or inequality. You can initialize a Type_info pointer variable with its address. (You cannot assign or copy a Type_info object or pass it as a function argument.) You can call the member function Type_info::name() to get a pointer to the type's name. You can call the member function Type_info::before() to get a 0 or 1 integer that represents the order of the type in relation to another type.
How would you use typeid? What purpose is gained by determining the specific type of an object? The dynamic_cast operator is more flexible in one way and less in another. It tells you that an object is of a specified class or of a class derived from the specified class. But to use it, the specified class needs at least one virtual member function. And dynamic_cast does not work with intrinsic types.
Consider a persistent-object database manager. It scans the database and constructs memory objects from the data values that it finds. How does it determine which constructors to call? RTTI can provide that intelligence. If the first component of a persistent-object record is the class name (or, better yet, a subscript into an array of class names), the program can use RTTI to select the constructor. Consider this example, where the database scanner retrieves the class name of the next object and calls the DisplayObject function. Example 4, where only three classes are recorded in the database, assumes that the database manager knows how to construct each object when the file pointer is positioned just past the type identifier in the record. This technique assumes that the scanner program knows about all the classes in the database and is similar to one that I use in the Parody object-database manager. I'll be discussing Parody in detail in future columns.
I am writing this column with Word for Windows 6.0 running as an OS/2-Win application, and I am not a happy scribe. I just spent three days installing OS/2. I spent those three days watching OS/2 and its installation procedure crash with regularity.
Associates have been telling me that I need OS/2, the better Windows than Windows, the better DOS than DOS, and the best environment for developing DOS and Windows applications. They smugly point out that their operating system is better than mine. OS/2 uses preemptive multitasking in protected mode, and applications cannot take out the operating system when they blow up. Just flush that sucker out and keep moving along. They say that high-tech programmers shouldn't piddle around with low-tech software like MS-DOS and Windows.
Until now, I organized programming projects into Windows Program Manager groups with icons for the word processor, the C++ compiler, the program itself, and so on. I jumped from task to task using Windows' simple multitasking capabilities. It worked well enough, but from time to time a program would blow up and take Windows, DOS, and anything I hadn't saved along with it.
A copy of OS/2 2.1 has been on the shelf for a couple of months now, and Borland's OS/2 C++ compiler arrived recently, something I've been wanting to try. You can install OS/2 all by itself or with DOS in a dual-boot configuration. You can use the OS/2 improved file system only if you do not use the dual-boot feature. Praise glory that I did not go the full high-tech route. I set up a DOS boot diskette just to be safe and left the FAT file system intact. A prudent battle plan includes the retreat. Right out of the box, the installation program died. It got just so far and left me staring at a dark screen. The installation manual said that it might do that--brand new software telling me to try it and see if it blows up. What will they think of next? OS/2 had decided that I had some particular video display configuration, so it installed itself accordingly. Then, when trying to use the video mode, OS/2 changed its mind and quit.
The procedure said to restart the installation, press Esc at a certain place, and run the SETVGA program to reconfigure everything to VGA, the lowest common video denominator. SETVGA asked me to insert Display Driver Diskette 1. But I was installing from a CD-ROM--and there is no Diskette 1. SETVGA, apparently not mindful of how things are being done, wants that diskette. I located a set of the 19 OS/2 installation diskettes and went from there. SETVGA ran for a while and said it couldn't find a file to complete the reinstallation. It didn't say which file, just that it couldn't find one.
I tried booting, which did not work. An error message said that OS/2 couldn't find VVGA.SYS, a clue to the previously unnamed missing file. Using my boot diskette and trusty, clunky old DOS, I determined that OS/2's CONFIG.SYS was trying to load a VVGA.SYS device driver, which was nowhere to be found. I couldn't find it on the hard disk, the diskettes, or the CD-ROM. There was a file named VSVGA.SYS, so I changed CONFIG.SYS to load that one. OS/2 booted to a dark screen again.
Eventually, the display problem mysteriously went away. I had been messing with the video adaptor's installation program and something I did made the display start working. I continued the installation, and it ran for a while and locked up. Several restarts had similar results but at different places.
After deleting everything and starting over, the installation went without a hitch, and OS/2 was running. Still, OS/2 has decided that my video display is 480x640 VGA only. I want 600x800, which Windows 3.1 under DOS finds perfectly acceptable. OS/2 refuses to install itself that way. When I finally got through the selective installation of Super VGA, OS/2 wouldn't run. The only high-resolution configuration that works is 8514 emulation, which produces a washed-out look that I don't like.
I set up a folder and installed Word for Windows 6.0 into it. Ideally, the first thing you do in the morning is start all the programs you use and leave them running as icons. Then you activate them when you need them and minimize them when you don't. With Word running, I pressed Alt+Tab to get back to the desktop, which is when the unthinkable happened--the system locked up tighter than a harp string. I had to reboot.
Next I installed WinCIS, the CompuServe Information System for Windows program. OS/2 claims to be able to handle communications in the background while you do other things, but the program will not run in the background without locking up OS/2. Likewise, the CD-ROM-based CorelDraw setup program crashes the system.
Before you OS/2 mavens rush to write letters telling me what I did wrong, how unfair I'm being, how OS/2 deserves a more thorough and comprehensive look-see, listen up. First, this isn't the whole story. I left out a lot of details because they're more crashingly boring than these. Second, software needs to be easy to install and use. Nobody should have to go through what I did, particularly not the typical, nontechnical user. The only reason I got it running is because, as a programmer, I know how to get around things, deduce the meaning of arcane system constructs, and find and try alternatives. Average users are not ready for this. Maybe OS/2 is great with powerful native OS/2 applications. But OS/2 is promoted as being able to run Windows applications in an armored environment. It doesn't. This highly touted, protected-mode, bullet-proof operating system cannot install itself or install and run three of my favorite Windows applications without crashing.
I'm using OS/2 now to develop C++ programs mainly to use the Borland compiler and the flat 32-bit memory model. OS/2's DOS emulation is indeed better than a Windows DOS box. Windows under OS/2 is definitely not better than Windows running under DOS. Programs take longer to load, they run slower, and the screen is jumpy and uncomfortable to use.
Things still crash the system. Quincy aborts OS/2 when the help database is not properly built. The GUI goes off somewhere into the ether, and full text-screen messages display with codes, memory addresses, and instructions to write everything down and call my service representative, whoever that is. Word for Windows 6.0 is sluggish and maims OS/2 from time to time. You can see it coming.
The user's manual and online help documentation don't help. I am still trying to figure out how to get a document on the desktop that associates with Word for Windows. Sure, tell me how easy it is, but try figuring out how when you don't already know.
Last week at the Borland International Conference, IBM had their usual array of PCs with OS/2 installed for test driving. I wanted to look into OS/2 word processors. They had WordPerfect, Ami Pro, and a third one whose name I forget. I tried the third one first. Guess what--it locked up OS/2. The attendant suggested that I move to another PC and not try that program again. I took his advice and moved to another PC--in a different exhibit.
Copyright © 1994, Dr. Dobb's JournalC++ Enhanced
Casts
dynamic_cast
static_cast
reinterpret_cast
const_cast
Run-time Type Information (RTTI)
OS/2: Seven Points on the Richter Scale
Example 1: Using the dynamic_cast operator.
class EditBox : public Control { ... };
void Paint(Control *cp)
{
EditBox *ctl = dynamic_cast<EditBox*>(cp);
if (ctl != 0)
// ctl points to an EditBox
else
// cp points to a non-EditBox Control
}Example 2: Using the static_cast operator.
class C : public A,
public B { /* ... */ };
B *bp;
C *cp = static_cast<C*>(bp);Example 3: Using the const_cast operator.
#include <iostreams.h>
class A {
int val;
int rptct; // number of times the object is reported
public:
A(int v) : val(v)
, rptct(0) { }
~A()
{ cout << val << " was reported " << rptct << " times."; }
void report() const;
} ;
void A::report() const
{
const_cast<A*>(this)->rptct++;
cout << val << '\n';
}
int main()
{
const A a(123);
a.report();
a.report();
a.report();
return 0;
}Example 4: Using RTTI.
void DisplayObject(char *cname)
{
if (strcmp(cname, typeid(Employee).name())==0) {
Employee empl;
empl.Display();
}
else if (strcmp(cname, typeid(Department).name())==0) {
Department dept;
dept.Display();
}
else if (strcmp(cname, typeid(Project).name())==0) {
Project proj;
proj.Display();
}
}