Outsmarting the compiler, and/or the Standard C and C++ libraries, can sometimes take a remarkable amount of trickery.
Copyright © 2000 Robert H. Schmidt
Growing up in southwestern Ohio, I was a devoted fan of the Cincinnati Reds major league baseball team. My interest in them peaked during my teenage years, which happily coincided with the Big Red Machine era of the 1970s. During that decade, the Reds played in four World Series, winning two of them.
This year marks the 25th anniversary of the dramatic 1975 World Series between the Reds and the Boston Red Sox. Three members of that Series Sparky Anderson, Tony Perez, and Carlton Fisk are being inducted into baseball's Hall of Fame this year. They join others from that Series who are already in the Hall: Johnny Bench, Joe Morgan, and Carl Yastrzemski [1].
Microsoft also turns 25 this year. The coincidence of these two silver anniversaries got me thinking: we should put together a programming Hall of Fame. Only our inspiration should come not from Cooperstown, but from the Hard Rock Cafe.
Imagine the scene: as you walk in, the cashier hawks T-shirts and trinkets leftover from last year's trade shows. The menu features Beta Burgers, Nanonachos, and KLOC cola culinary delights priced at twice their worth. As you dine, you gaze in wonder at Grace Hopper's moth flambé, Bill Gates' first subpoenaed email, Dan Saks's last CUJ column, and other programming relics.
I think the arrival of such a place would be a big hit here in Redmond. Until then, I'll content myself with the authentic (if less sublime) geeky shop-talk ambience of Microsoft's cafeterias.
Refactoring Factories
Q
Hello,
Although your answer to Volker Lerman (in your June 2000 column) is correct, it doesn't address all of his concerns, among which was "I want to make the pointers' behavior transparent to the class user." Obviously, one cannot enforce the B *const declaration; but one can make it easily available as a typedef:
struct factory { // ... typedef B *const built_type; // ... };Now the declaration looks natural, as he wants:
factory::built_type b1 = factory::create();Thanks for the interesting column, as usual. Frank Griswold
A
The item you refer to is "Context-Free const" from that June column. In that item, I discussed Volker's factory function, and the nature of the pointer returned by that function. I centered on Volker's desire for the pointer's constness to somehow propagate to other pointers, since that seemed to be the thrust of his question. Your perspective is that Volker also wanted to hide the entire pointer nature of the factory's returned value.
If Volker hides the pointer in a typedef as you show, and if his users consistently use the typedef name instead of the raw pointer name, then the constness problem he encountered will go away. Unfortunately, that solution relies completely on programmer discipline, since the compiler gladly accepts all of
factory::built_type b1 = factory::create(); B const *b2 = factory::create(); B *b3 = factory::create();A more robust solution, which for some reason didn't occur to me until now, is:
struct B { mutable int id; }; struct factory { public: static B const &create() { return *new B; } };Now the const properties hold as Volker wants:
B b1; B &b2 = factory::create(); // error B const &b3 = factory::create(); // OK b3.id = 1; // OK b3 = b1; // errorResuming Exceptions
Q
About your column talking about function try blocks, I have to admit that function try blocks were really new to me, so I quickly jumped on CodeWarrior and tried to compile the code. Having played with it a few times, I came across a way to work around the problem of the exception being rethrown in the catch handler, by using a ... goto. (Damn I never thought gotos could be useful in C/C++.) See the attached code.
Of course I guess that such code might fail if container was not composed of only one member but two or more, in which case some of the constructors would probably never be called where we could expect them to be. Furthermore, this also confuses destructors of those members, for example if member::~member() isn't handling well the destruction of a semi-constructed member object, we might run into big trouble. Christopher Allene
A
My version of your code appears as Listing 1. When built and run with both Metrowerks CodeWarrior Pro for the Mac, and the EDG translator atop VC++, the program produces
throw from member ctor catch in container ctor continuing in container ctoras you desire.
Listing 1 won't build with Visual C++, which doesn't support function try blocks. Accordingly, I changed the example to use a normal try block instead of a function try block. The result is Listing 2, which does build with Visual C++.
While your technique apparently works with these systems, it suffers two large problems:
- The program is ill-formed and shouldn't build at all.
- Even if the program weren't ill-formed, it would still be ill-advised.
In subclause 15/2, the C++ Standard is quite clear on the first point:
A goto, break, return, or continue statement can be used to transfer control out of a try block or handler, but not into one.Of the translators I've tried, the only one to get it right is EDG in its "strict" mode:
error #656: transfer of control into a try blockAssume for the sake of argument that EDG is wrong, and that the programs are actually well formed. As you have already guessed, the programs still suffer a giant problem: both container and member are constructed with fractured states. In effect, the objects are fully constructed physically, but only partially constructed logically.
This presents a host of difficulties, most notably during destruction. Destructors are called only for fully constructed objects. Since container and member are fully constructed, their destructors are called even though some parts of those objects ought not be destroyed.
To make this technique viable, every object would have to record how much of itself is intact, then ensure that only those parts are referenced and destroyed. For a simple case such as yours, the instrumentation is not heinous; but for a more complex object, especially one containing array and base-class subobjects, the technique would be untenable.
Oops!...I Did It Again
Q
How do I get information about an exception (who threw it, what caused it, etc.) when handling all exceptions in one handler? V. Krishnan
A
You don't, at least not directly.
When a handler catches an exception, it has no innate knowledge of who threw the exception or why. No Standard Library function or object tracks the exception's origin. At best, a handler is guaranteed to know only the type and value of the thrown exception.
At the same time, nothing prevents you from throwing "smart" objects. Such objects can directly contain, or hold pointers to, a context describing their origin and purpose. Listing 3 shows a simple example.
You can extend this technique to allow an audit trail: instead of remembering just a single creator and reason, the exception could also maintain a list of every function it passed through on its way up the call stack provided those functions caught, tagged, and rethrew the object. I leave that extended version as an Exercise for the Reader.
For any of these techniques to work, you cannot catch via
catch (...)Thrown exceptions are unnamed temporary objects. When you catch an exception via
catch (oops const &e)you bind the local name e to at least a portion of the thrown temporary [2]. However, when you catch via
catch (...)you have no opportunity to name the exception, or even know the exception's type.
Pencil Sharpener
Q
Hello there.
I don't know if you do questions on the Standard library, but I thought this question (actually the answer) may be of some general interest:
I've just finished writing what must be the two-millionth date class ever written. Having admitted this, you know that I'm not the sharpest pencil in the box. So, my question is hopefully easy for you. How do you obtain from the "locale" the order in which to print the components of a date? That is, should it be MM/DD/YY, or should it be DD/MM/YY, etc. Also, what is the desired separator character?
The localeconv function will tell more than you ever wanted to know about formatting money, but nothing about formatting dates (or times). Am I not allowed to format dates myself or does the Standard require me to use strftime? Norm Olsen
A
I assume that you are using Standard C, since your question mentions only routines available in the Standard C Library. If you confine yourself to that library, then you have no direct way to extract a locale's preferred date ordering. As you already know, the library will format the date for you via strftime; however, you apparently want to format the date yourself in a locale-correct way.
With the C Library, I can think of only one (indirect) way to extract a locale's date ordering: create the locale-aware date via strftime using the %x format, parse the result to determine its field ordering, and then map that ordering to a set of possible orderings (probably in an enumeration).
My algorithm assumes that you can reliably scan and parse the formatted date. While this assumption may be true for your particular environment, it is not generally true across platforms, especially since locales beyond the "C" locale are not portable.
If you are programming in C++, you have a more pleasant option, for the Standard C++ Library lets you directly discover the a locale's date ordering:
#include <locale> std::locale loc("C"); typedef std::time_get<char> tg; tg const &facet = std::use_facet<tg>(loc); std::time_base::dateorder order = facet.date_order();What this all means:
- std::locale loc("C") creates an object encapsulating the "C" locale's information. I pick the "C" locale because it's guaranteed to exist on all implementations. You can replace "C" with your locale name of choice.
- std::time_get<char> is a locale facet, instantiated from the class template std::time_get. tg is my typedef shorthand for std::time_get<char>.
- std::use_facet<tg> is an instantiation of the function template std::use_facet.
- The call std::use_facet<tg>(loc) returns the tg facet of loc. I store that returned facet in facet. The Standard guarantees that tg (which is really time_get<char>) exists as a facet of each locale; thus, I didn't have to test for its existence in loc.
- facet's members mostly scan and parse streams of chars, and then convert the parsed chars to date fields. However, one facet member (date_order) doesn't parse anything, but instead yields the preferred date ordering.
- I save that preference in order, which has the enumeration type std::time_base::dateorder. Possible values within the enumeration are no_order, dmy, mdy, ymd, and ydm.
By the time the dust settles, order contains one of five enumeration values, depending on loc's preferred date ordering.
You also wanted to know a locale's preferred date separator. If there's a clean way to figure that out, I sure can't find it. My research shows no evidence that a locale holds any notion of a date separator. I invite Diligent Readers who know better to enlighten me [3].
Notes
[1] Had he not been banned from baseball, Pete Rose who was also part of the '75 Reds certainly would be in the Hall by now.
[2] If the original thrown object were of type oops, then e would bind to that entire object. If the thrown object were of a type derived from oops, then e would bind only to the oops portion of that object.
[3] If you're desperate enough, you can reverse engineer the separator with the strftime trick I described earlier.
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 BobbySchmidt@mac.com.