Style Case Studies: Construction Unions - Dissecting Construction Unions
(Page 3 of 9 )
3. The article [Manley02] cites the motivating case of writing a scripting language: Say that you want your language to support a single type for variables that at various times can hold an integer, a string, or a list. Creating a union { int i; list<int> l; string s; }; doesn’t work for the reasons covered in Questions 1 and 2. The following code presents a workaround that attempts to support allowing any type to participate in a union. (For a more detailed explanation, see the original article.)
On the plus side, the cited article addresses a real problem, and clearly much effort has been put into coming up with a good solution. Unfortunately, from well-intentioned beginnings, more than one programmer has gone badly astray.
The problems with the design and the code fall into three major categories: legality, safety, and morality.
Critique this code and identify:
a) Mechanical errors, such as invalid syntax or nonportable conventions.
b) Stylistic improvements that would improve code clarity, reusability, and maintainability.
The first overall comment that needs to be made is that the fundamental idea behind this code is not legal in standard C++. The original article summarizes the key idea:
The idea is that instead of declaring object members, you instead declare a raw buffer [non-dynamically, as a char array member inside the object pretending to act like a union] and instantiate the needed objects on the fly [by in-place construction].—[Manley02]
The idea is common, but unfortunately it is also unsound.
Allocating a buffer of one type and then using casts to poke objects of another type in and out, is nonconforming and nonportable because buffers that are not dynamically allocated (i.e., that are not allocated via malloc or new) are not guaranteed to be correctly aligned for any other type than the one with which they were originally declared. Even if this technique happens to accidentally work for some types on someone’s current compiler, there’s no guarantee it will continue to work for other types or for the same types in the next version of the same compiler. For more details and some directly related discussion, see Item 30 in Exceptional C++ [Sutter00], notably the sidebar titled “Reckless Fixes and Optimizations, and Why They’re Evil.” See also the alignment discussion in [Alexandrescu02].
For C++0x, the standards committee is considering adding alignment aids to the language specifically to enable techniques that rely on alignment like this, but that’s all still in the future. For now, to make this work reasonably reliably even some of the time, you’d have to do one of the following:
- Rely on the max_align hack (see [Manley02] which footnotes the max_align hack, or do a Google search for max_align).
- Rely on nonstandard extensions like Gnu’s __alignof__ to make this work reliably on a particular compiler that supports such an extension. (Even though Gnu provides an ALIGNOF macro intended to work more reliably on other compilers, it too is admitted hackery that relies on the compiler’s laying out objects in certain ways and making guesses based on offsetof inquiries, which might often be a good guess but is not guaranteed by the standard.)
You could work around this by dynamically allocating the array using malloc or new, which would guarantee that the char buffer is suitably aligned for an object of any type, but that would still be a bad idea (it’s still not type-safe) and it would defeat the potential efficiency gains that the original article was aiming for as part of its original motivation. An alternative and correct solution would be to use boost::any (see below), which incurs a similar allocation/indirection overhead but is at least both safe and correct; more about that later on.
Attempts to work against the language, or to make the language work the way we want it to work instead of the way it actually does work, are often questionable and should be a big red flag. In the Exceptional C++ [Sutter00] sidebar cited earlier, while in an ornery mood, I also accused a similar technique of “just plain wrongheadedness” followed by some pretty strong language. There can still be cases where it could be reasonable to use constructs that are known to be nonportable but okay in a particular environment (in this case, perhaps using the max_align hack), but even then I would argue that that fact should be noted explicitly and, further, that it still has no place in a general piece of code recommended for wide use.
This chapter is from Exceptional C++ Style, by Herb Sutter (ISBN 0201760428, copyright 2005. All rights reserved. It is reprinted with permission from Addison-Wesley Professional). Check it out at your favorite bookstore today.
Buy this book now. |
Next: Into the Code >>
More Code Examples Articles
More By Addison-Wesley/Prentice Hall PTR