Chapter 18. Common Mistakes

This chapter contains the following sections:

Rogue Wave libraries are built by developers like you who understand the frustration of programming errors. We try hard to fine tune our libraries to minimize these errors. Nevertheless, the complexity of C++ affords countless opportunities for making some very subtle mistakes.

In writing this section, we went though our technical support documents to uncover the most common mistakes our users were reporting. When we found mistakes that could be prevented, we tried to rewrite the library to make them impossible. This is always the best approach. We can't always follow it, however, if unacceptable performance degradations result, or the language itself prohibits the change.

This section summarizes the most common mistakes that are left over after we fixed the ones we could. If you're having a problem, take a look through this list and, of course, be sure to read the pertinent parts of the manual.

Redefinition of Virtual Functions

If you subclass off an existing class and override a virtual function, make sure that the overriding function has exactly the same signature as the overridden function. This includes any const modifiers!

This problem arises particularly when creating new RWCollectable classes. For example:

class MyObject : public RWCollectable {
public:
  RWBoolean isEqual();                            // No "const" !
};

The compiler will treat this definition of isEqual() as completely independent of the isEqual() in the base class RWCollectable, because it is missing a const modifier. Hence, if called through a pointer:

MyObject obj;
  RWCollectable* c = &obj;
  c->isEqual();       // RWCollectable::isEqual() will get called!

Iterators

Since the drafting of the ANSI/ISO Standard C++ Library, there are now two kinds of iterators available for use in Tools.h++: the traditional iterators which we describe in detail throughout this manual, and the new "Standard Library" iterators. For more information about using the iterators now mandated by the standard, we refer you to the manual which came with your version of the Standard C++ Library. In this Tools.h++ manual, unless we specifically say otherwise, an iterator refers to a traditional iterator.

Immediately after construction, the position of a Tools.h++ iterator is formally undefined. You must advance it or position it before it has a well-defined position. The rule of thumb is "advance and then return." If the end of the collection has been reached, the return value after advancing will be special, either FALSE or nil, depending on the type of iterator.

Hence, the proper idiom is:

RWSlistCollectables ct;
RWSlistCollectablesIterator it(ct);
 
.
.
.
 
RWCollectable* c;
while (c=it()) {
  // Use c
}

Return Type of operator>>()

An extremely common mistake is to forget that the functions:

Rwvistream&  operator>>(RWvistream&, RWCollectable*&);
RWFile&      operator>>(RWFile&,     RWCollectable*&);

return their results off the heap. This can result in a memory leak like the following:

main(){
   RWCollectableString* string = new RWCollectableString;
   RWFile file("mydata.dat");
 
   // WRONG:
   file >> string;                          // Memory leak!
 
   // RIGHT:
   delete string;
   file >> string;
 
}
 

Avoid Persisting Value Collections of Pointers

It may sometimes be reasonable to collect pointers into a value-based collection in order to deal with identies instead of values. However, you should never attempt to persist them, since a collection with value semantics will simply store the values of the pointers into the stream, rather than storing the information pointed to. If you were to pull those old pointers out of the stream and back into memory, they would almost surely point to invalid locations.

Include Path

When you specify an include path to the Rogue Wave header files, make sure that it does not include a final rw:

#  Use this:
CC -I/usr/local/include -c myprog.C
 
#  not this:
CC -I/usr/local/include/rw -c myprog.C

Match Memory Models and Other Qualifiers

When it comes time to link your program to the Rogue Wave library, make sure that all the following match: qualifiers, compilation "mode" macros (such as RWDEBUG and RW_MULTI_THREAD), the choice to use (or not use) shared libraries, and memory models. For example, if you compiled using the flat memory model, make sure you are linking to a library compiled with the flat memory model. Similarly, if you are using a debug version of the library, be sure to compile your programs with the same debug settings (see "The Debug Version of Tools.h++").

Failure to do so will result in the linker emitting mysterious "undefined external reference" or other errors, or even worse, you may find that programs run, but do not execute as expected.

Keep Related Methods Consistent

When you design classes that will be used with the Tools.h++ library, you may be tempted to take short cuts, like providing a simplistic hash method, or operator<(), since you "know it will never be used anyhow." Decisions like this can have disastrous maintenance consequences later. Unless you have a very good reason, it makes sense to ensure, for example, that operator<(), operator==(), compareTo(), and isEqual() are based on the same information. You must also be sure that values which are isEqual() or == with each other have the same hash value, since otherwise they will never be found if placed into a collection that uses hashing techniques. A little extra effort at the beginning can pay big dividends in reduced debugging time later on!

DLL

Because the DLL version of Tools.h++ uses the large memory model, any data segment that uses it must be fixed. For example, if you were to create an RWCollectable object in your data segment and insert it into a Tools.h++ collection, that collection will be holding a four byte pointer. If your data segment were to move, the pointer would no longer be valid. Hence, be sure that your .DEF definition file has a line similar to the following:

DATA  PRELOAD FIXED

Note that with Microsoft's decision to abandon real mode Windows, working with fixed data and global memory is no longer the problem it used to be. The extra level of indirection offered by protected mode allows data to be moved around in physical memory without invalidating selectors. The entries in the descriptor table are changed instead.

Use the Capabilities of the Library!

By far the most common mistake is not to use the full power of the library. If you find yourself writing a little "helper" class, consider why you are doing it. Or, if what you are writing is looking a little clumsy, then maybe there's a more elegant approach. A bit of searching through the Tools.h++ manual may uncover just the thing you're looking for!

Here's a surprisingly common example:

main(int argc, char* argv[]){
   char buffer[120];                   //uh oh: possible overflow
   ifstream fstr(argv[1]);
   RWCString line;
 
   while (fstr.readline(buf,sizeof(buf)) {
     line = buf;                                //hmm: extra copy
     cout << line;
   }
}

This program reads lines from a file specified on the command line and prints them to standard output. By using the full abilities of the RWCString class it could be greatly simplified as follows:

main(int argc, char* argv[]){
   ifstream fstr(argv[1]);
   RWCString line;
 
   while (line.readLine(fstr)) {
     cout << line;
   }
}

There are countless other such examples. The point is, if it's looking awkward to you, most likely there's a better way!