Bobby deals this month with various kinds of passages, including a very difficult personal one.
Copyright © 1999 Robert H. Schmidt
Timed flies: In high school I raised colonies of Drosophila melanogsaster (a.k.a. fruit flies) for a genetics class. We did time the flies, or at least timed how long they'd be knocked out. At one point we ran out of ether anesthetic. Fortunately we took the class in the winter; as a surrogate anesthetic we bought carburetor anti-freeze, which had ether as its active indredient.
Time flies: As a little kid I hardly believed that one day I'd get to live in a time called 20-something. Everything and everyone I knew came from the era of 19-something. It all seemed impossibly remote; yet now that I'm older than my parents were then, it seems rather normal and Y2K frenzy aside anticlimactic.
Time flew: I missed last month's issue attending to family business. My mother has cancer, and will be gone by the time you read this. Our family have been through the traditional accommodation steps, starting with denial and leading to our present acceptance. Unlike many families, my parents and I don't really have old hurts and lingering unfinished business; so while we are sad, we aren't suffering guilt or regret with one exception.
Time's flown for Mom faster than she expected (she's only 60). Time's flown, but she hasn't; I've been in more airplanes in the past three years than she's been in her whole life. In particular, she never made the time to visit me in Redmond. She always wanted to, but let other things get in the way, reckoning she'd have more time.
When I was growing up, our living room sported a wall-size mural of a lush green forest. Mom adored that mural. We didn't know it then, but the mural was of the Hoh Rain Forest on Washington's Olympic Peninsula, close to where I live now. She wants her final earthly resting place to be near the trees from that mural. I guess she'll finally come "visit" me after all.
Much of the personality you see in my columns has come to me from her. That I had the courage to write publicly in the first place I largely attribute to her faith in me. As a small gesture of what she has and will continue to mean, I want to dedicate this year's columns to the memory of Carole Madaline Schmidt.
Deconstruction
Q
Bobby,
I have a question about exceptions. When I run the attached code (Listing 1) I get this result:
Test 1: specification empty error() ~error() caught ~error() Test 2: specification 'throw()' error() caught ~error()(The compiler is VC++ 6.0.)
Why does the exception get destroyed before it gets caught? I thought that if a function did not declare throw(), then it was declaring that it could throw any object.
Thanks in advance Andy Brummer
A
I've included my version of your code as Listing 1. That code, and the run-time results, bring up some interesting points about exception handling.
At first the results indeed look puzzling. In Test 1, only one error object is apparently constructed, yet two are destroyed; while in Test 2, only one object appears to be constructed and destroyed. To help separate appearance from truth, I augmented error to show the object address associated with each function call:
class error { public: error() { printf("%p error()\n", this); }; ~error() { printf("%p ~error()\n", this); }; };The new results [1]:
Test 1: specification empty 0065FD7C error() 0065FD7C ~error() caught 0065FD80 ~error() Test 2: specification 'throw()' 0065FD88 error() caught 0065FD8C ~error()are more revealing:
- In Test 1, the original error object is constructed then destroyed. Along the way, some other object is silently constructed, then destroyed after the exception is caught.
- In Test 2, what originally looked to be a single object construction/destruction is actually construction of one object and destruction of another.
In both cases, some object is apparently destroyed without being constructed first. I'm going to focus on Test 1, although I suspect the same "problem" underlies both tests. (Test 2 also exposes a somewhat related deficiency in VC++ that I'll discuss shortly.)
As you surmise, the lack of exception specification on test_1 implies that the function can legitimately throw anything. Consistent with that implication, test_1 does indeed throw an exception object (0065FC7C), which main dutifully catches. By the time the exception handler is entered, the thrown object is destroyed a sequence you find puzzling.
Adding to the confusion is the apparent destruction of an object (0065FD80) that was never constructed. Unless VC++ is playing by language rules I don't know about, the number of constructions should be >= the number of destructions. Since your declared constructor isn't getting called, some other constructor must be.
Recall the Miranda Rules of C++: if you don't explicitly provide certain member functions, the translator will often implicitly provide them for you. Among those provided functions is the copy constructor, which your class omits. Following the Miranda Rules, the translator acts as if you had really declared
public: inline error(error const &) { };I suspect that the "synthesized" copy constructor induces your confusion. Adding an explicit and traced copy constructor should clear things up:
class error { public: error() { printf("%p error()\n", this); }; error(error const &) { printf ("%p error(error const &)\n", this); }; ~error() { printf("%p ~error()\n", this); }; };Or maybe not. Consider the results:
Test 1: specification empty 0065FD8C error() caught 0065FD8C ~error() Test 2: specification 'throw()' 0065FD8C error() caught 0065FD8C ~error()Now the extra constructions and destructions are gone! Counter-intuitively, making the implicit copy constructor explicit kept it from being called. What's going on?
When an object is thrown, the translator can make a temporary copy of the thrown object. That temporary in turn initializes the local handler object [2]:
throw t; // Exception #1 is thrown. // Temporary Exception #2 // initialized from // Exception #1. // ... catch (E &c) // Exception #3 // initialized from // Exception #2.In many cases, however, the translator is allowed to omit the temporary exception, and can instead initialize the handler's exception directly from the original thrown exception [3]. That's what VC++ is doing here. As a result, only one exception object is constructed/destroyed in each test.
By the way, all of this object construction implies that you should catch exceptions by reference
catch (X &x2) // goodand not by value
catch (X x2) // badThat way, your handler binds to an existing exception object instead of needlessly cloning that existing object.
Now to the VC++ deficiency: function test_2 violates its exception specification by throwing an exception it promised not to. According to the Standard, such violation should result in the Standard Library function unexpected being called [4]. From what I can tell, VC++ does not properly call unexpected; otherwise, your program would never enter the catch clause following the call to test_2.
No Construction
Q
Hi Bobby,
I'm a longtime reader, first time emailer.
I realize that this problem isn't explicitly covered in the Standard, but...
I have a class that performs some data registration when it is constructed:
class fred { fred() { RegisterData(); } void RegisterData(); };As a stand-alone module, it works fine. Now, move this module into a static .LIB, and add a global instantiation:
fred register_class;This way, whenever you link to the .LIB, the data will be automatically registered at startup time.
Clever, eh? Well, no. Using either Visual C++ 6 or Borland C++ Builder 4, this global class is optimized away during the link phase, even if you have optimizations turned off. Essentially you must reference the global object somewhere in your main program (not another .LIB) for it to be actually instantiated and constructed even though by definition the global object should always be there.
I've read on the Internet that the Solaris compilers used to behave this way, but now will actually construct the global object.
So my question: is this incorrect behavior on the part of the compiler, or incorrect design on my part?
Thanks Charles Cafrelli
A
In a couple of non-normative notes [5], the C++ Standard allows newly translated code to link with previously translated code:
A C++ program need not all be translated at the same time.
Previously translated translation units and instantiation units can be preserved individually or in libraries. The separate translation units of a program communicate by (for example) calls to functions whose identifiers have external linkage, manipulation of objects whose identifiers have external linkage, or manipulation of data files. Translation units can be separately translated and then later linked to produce an executable program.
However, the Standard does not detail how a translator creates and uses such libraries. I don't know if Microsoft has special semantic rules for code in static libraries. For now I'll assume that if a VC++ program is Standard conforming to begin with, it stays that way when some parts are moved into a static library.
Listing 2 shows a small sample demonstrating your problem. This sample contains three source files:
- other.h defines other_class.
- other.cpp implements other_class and defines other_object.
- main.cpp contains an empty main.
I created a new VC++ console application project, included these three source files, then built and ran the program. According to your theory, other_object should still be initialized even though it is not explicitly referenced in main. Tracing with the VC++ debugger, I found that other_object was indeed being initialized, contrary to your findings.
To fully replicate your scenario, I needed to create two projects: one static library to house other_object, and a console application defining main and linking to this library. Once I partitioned the source among these two projects, I built the library, then built the application. Debugging once again, I was finally able to reproduce your results: execution never flowed into the other_class constructor.
That you find this behavior distasteful does not imply that the behavior is incorrect. After a few minutes of poking about the Standard, I discovered this [6]:
It is implementation-defined whether or not the dynamic initialization of an object of namespace scope is done before the first statement of main. If the initialization is deferred to some point in time after the first statement of main, it shall occur before the first use of any function or object defined in the same translation unit as the object to be initialized.
An object defined in namespace scope having initialization with side-effects must be initialized even if it is not used.
In our case, VC++ may defer construction of other_object unless or until one of these conditions is met:
1. Some other object or function in other_object's translation unit is used.
2. The act of constructing other_object causes side effects.
Our program meets neither of these conditions:
1. No other function or object is defined in the same translation unit as other_object. Thus, there's no opportunity for "the first use" of such an object or function to cause other_object "to be initialized."
2. The other_class constructor has no side effects.
Verdict: VC++ is allowed to defer construction of other_object indefinitely. Note that deferment is allowed, but not required. In the stand-alone version, construction was not deferred; in the library version, construction was deferred. Based on my interpretation of the Standard, both behaviors are correct.
I leave this as an Exercise for the Student: modify both the stand-alone and library versions of my example to meet either of the above two conditions, then determine if the resulting programs are Standard conformant. Hints:
- To meet condition #1, add an external other_func or some such to other.cpp, then call other_func from main.
- To meet condition #2, call printf from the other_class constructor [7].
Erratica
Two more Diligent Reader responses to my "Chicken and Egg" topic from the July 1999 CUJ:
Karim Ratib found a similar problem discussed in a "Stump the Wizard" entry on Rogue Wave's site. The relevant URL is <http://www.roguewave.com/products/resources/sw/sw002/sw002.html>.
Mike Suman suggested the problem is confusion of function name use and mention. He ended his mail with this thought:
"If Marlon Nelson were actually allowed by his compiler to execute f=f() for a function which returns a pointer to itself, his computer would write an address over the functions's code. I get a vision of a great Monty Python hand coming suddenly out of the sky with a rubber stamp and flattening the lowly programmer with his name."
Barring some piercing insight from another Diligent Reader, I'll let that same great hand squish this topic underground and into hibernation.
Notes
[1] These are the object addresses on my machine. Your mileage may vary. [2] C++ Standard subclause 15.1/3 ("Throwing an exception"). [3] Subclause 15.1/5.[ [4] Subclause 15.5.2 ("The unexpected() function"). [5] Mainly Clause 2/2 ("Lexical conventions"). [6] Subclause 3.6.2/3 ("Initialization of non-local objects"). [7] According to subclause 1.9/7 ("Program execution"), "calling a library I/O function" qualifies as a side effect. printf also lets you trace the construction without a debugger.Bobby Schmidt is a freelance writer, teacher, consultant, and programmer. He is also an alumnus of Microsoft, a speaker at the Software Development and Embedded Systems Conferences, and an original "associate" of (Dan) Saks & Associates. In other career incarnations, Bobby has been a pool hall operator, radio DJ, private investigator, and astronomer. You may summon him on the Internet via rschmidt@netcom.com.