Al is a DDJ contributing editor. He can be contacted at [email protected].
It's the middle of December, 1999, as I write this, so the full impact of Y2K is unknown. By the time you read these words, we'll all know what happened, but for now I'm still in the dark. I eagerly look forward to the day when this subject is old news. Until then, I guess I'll milk it for all it's worth.
Until now, it never bothered me that I communicate with the future when I write these columns; the future never held anything as unpredictable as what the media call the "Y2K computer bug." I've remained amused and skeptical about the hype and hysteria throughout all this. Slow news days are a problem for the media as they struggle to come up with twenty-four hour news. Without something sensational to report, they have to contrive something. They can't count on the president to keep us titillated all the time, so they need something to fill the space. These past few months it's been Y2K. Next month, it'll probably be about how this month's dire predictions fizzled. I hope so.
Like the atheist who gets born again when his time is near "just in case," we Y2K skeptics reveal our hypocrisy more and more as December winds down. Yesterday, I cranked up the Titanic (our RV), drove it down to the local C-store, and filled its 90 gallon tank with diesel fuel. Next stop was the propane company to make sure we could cook, come what may. Back home, I drained, sanitized, and filled the water supply and stocked the refrigerator. The Titanic is ready for the millennium and other disasters. I did all that because prophets of doom have suggested that Y2K might shut down all the gas pumps and utility companies in the world. I did it "just in case" they are right. Having publicly derided the prophets as I have, this reaction makes me feel guilty and hypocritical, so I need something to justify my actions. I need a rationalization to keep my neighbors and wife from making disapproving cluck-clucking noises and laughing behind my back and in front of my front. Judy suggests that my next trip for provisions includes stocking up on a bountiful supply of crow, which, according to her, will be the main staple of my diet next month no matter what happens.
In the 1950s, many citizens reacted to the Red Scare by building and stocking bomb shelters. Our government scared them into doing it. History repeats itself and NASA has provided the rationalization I need to justify my contemporary version of the bomb shelter, the well-prepared Titanic, the Y2K shelter on wheels.
What has NASA got to do with it, you ask? Tonight the Space Shuttle is scheduled to blast off. The mission has been delayed several times because of bad wiring. The weather does not look good for a launch. The launch window extends until the day after tomorrow. According to official sources, if they cannot launch by then, they will not launch until after the first of the year. Are you ready for this? NASA does not want all that expensive hardware and those astronauts in orbit at the turn of the century "just in case." They fear that a Y2K computer bug could compromise their safety and the integrity of the mission.
I don't know how much it costs to put on hold for two weeks the orbiter, the boosters, the launch facility, and the personnel, "just in case," but I'm sure the number has lots of zeros on the right and at least two commas. If NASA is that unsure about the problem, who am I to laugh at my neighbors for stocking up with a few canned goods? Of course, it's probably all for naught. Nothing bad is really going to happen.
Excuse me. I'll be back in a few minutes and continue this column. I've got to get some cash from the ATM and fill up the van. Just in case.
The Trouble with Iterators
I'm back. The problems I have most often with STL containers has to do with iterators. The first problem is that there is no such word, and my spell checker keeps choking every time I write anything about the Standard C++ Library. That's nothing new. Technology creates words that did not exist before. Programming has given us many such words -- "instantiate," "preprocessor," and "stringizing" are examples. Older dictionaries did not define "initializer," either, or permit "data" to be a singular noun. That's changed. See how influential we programmers are upon the culture of our civilization. It's enough to make one proud.
The other problem with iterators is that they lull me into a false sense of security, and I keep repeating the same stupid bug. It's my problem, not the library's. Iterators are pointer-like mechanisms that point to objects of the type that the container contains and that, presumably, point to one of the objects in the container. They do, that is, if you use them correctly.
Iterators abstract the concept of object pointers without, presumably, the errant potential that pointers have for pointing off into the wild blue yonder. I say "presumably" because you must properly initialize iterators and use them according to the rules. You must ensure that iterators never exceed the limits as defined by the container class's begin and end member functions. Iterators lure you into thinking they are similar to what have been called "smart pointers" but they aren't all that smart. A generic iterator will behave properly according to the kind of container with which it is associated, assuming that its contents properly point to one of the objects in the container. If you try to use an iterator in a way that the container itself does not support, the compiler issues a diagnostic. It's not always anything readable, but it is a diagnostic nonetheless, and after you have some experience with standard containers and templates in general, you come to recognize those long, cryptic error messages for what they really mean. Iterators are at the foundation of generic programming. But that's about as smart as they get.
The trouble with iterators is that their validity is perishable. If you use one to step through a container, the iterator's value is reliable as long as you don't modify the container. If, for example, during the iteration you insert an object into the container, the system might allocate memory to increase the size of the container and make room. This process seldom leaves the container in its original location, which means that all existing iterators are no longer valid.
Consider Example 1. After that push_ back, the container is relocated and the iterator, which has not changed, now points into the ether. The symptom shows up the next time the program tries to use the iterator. A crash typically happens with the program counter pointing somewhere deep inside an STL function. This behavior is understandable, but it keeps taking me by surprise because I subconsciously expect iterators, with all their intelligence about what else they can do, to be smarter than that.
If you were maintaining your own dynamically growing arrays and using pointers, you'd know intuitively not to expect them to adjust when you relocated the data. But something about the generic programming model makes me want iterators to do better, to be smarter. The solution, of course, is to not use iterators that way but to treat them as momentary conveniences rather than persistent indexes. Example 2 fixes Example 1 by using a subscript rather than an iterator, which works for sequence containers that overload operator[]. I can't use this solution for all kinds of containers, however. I have to be more careful about how I use iterators.
A truly smart iterator would include assertions every time the program dereferences an object through the iterator or changes the value of the iterator itself. The assertions would ensure that the iterator points into a container of the class and type for which the iterator is declared. Otherwise, the iterator would throw an exception. Implementing such a check would not be trivial. The assertion could, at the very least, use typeid to ensure that the iterator points at an object of the parameterized type, but that wouldn't work if the iterator pointed at end, which is logically one past the last object in the container. An iterator has no physical connection to a specific container. Nothing in the generic programming model makes that association. An iterator points to an object, which we assume is in a container, but nothing tells the iterator which container hosts the begin and end member functions that define the container's boundaries. There are a couple of ways to approach the problem. One way would add an optional parameter to an iterator's declaration, one that specifies the container that the iterator is intended to support. Another way ensures that containers always include an anonymous null object that the end member function points to so that typeid always works. These are, of course, strawmen solutions. Much more work is needed to ferret out the problems and resolve them.
Smart iterators are a nice idea, but there's a cost. Some iterators are implemented as simple pointers. Adding the kind of intelligence I desire would help to debug programs but would also compromise efficiency. Assuming we could solve the problem of associating iterators with containers, the compiler could invoke smarter iterators when NDEBUG is not defined. Just as std::assert becomes a null operation when NDEBUG is defined, smart iterators would likewise give way to the more efficient versions in the nondebug compilation. This vague proposal would not violate the standard in any way or require an update to the standard definition. It might raise the ire of some C++ library developers who take offense when you suggest the use of old C idioms such as NDEBUG, particularly because it involves the hated preprocessor. It would, however, help us C++ programmers use this relatively new and exceedingly complex development environment called "Standard C++."
Maybe a better idea would be for me to knuckle down and quit coding that same stupid bug. There's a good new century's resolution.
DDJ