Using the shape_area function dramatically simplifies the
largest function, as shown in Listing Two.
Listing Two: A function that finds the largest shape in an array of shapes, using the shape_area function.
shape const *largest(shape const s[], size_t n) {
shape const *p = NULL;
double max = -1;
size_t i;
for (i = 0; i < n; ++i) {
double area = shape_area(&s[i]);
if (area > max) {
max = area;
p = &s[i];
}
}
return p;
}
Correctness and Maintenance Problems
One of the problems with discriminated unions is that they require a lot of discipline to ensure that you don't access a union member unless it's the currently active member as indicated by the discriminator. In most cases, this means you should be scrupulous about wrapping the accesses to union members inside if- or switch-statements that test the discriminator.
Unfortunately, nothing in C prevents accidents such as:
case sk_rectangle:
area = s->u.triangle.side1
* s->u.triangle.side2;
~~~
This code will compile and then produce unpredictable, possibly erroneous, run-time results.
Some languages, such as Ada, have built-in support for discriminated unions intended to prevent such mishaps. Andrei Alexandescru showed how you can implement safe discriminated unions in C++ using template metaprogramming techniques (see his Generic <Programming> columns Discriminated Unions (I) and Discriminated Unions (II)).
Another problem with discriminated unions is that they can easily lead to
code that's hard to maintain. For example, in addition to the
shape_area function, your application might include other shape
operations such as shape_perimeter, shape_resize,
or shape_put. Each function has a similar, if not identical,
switch-statement structure as shape_area. If you add a new
shape, or modify the attributes of an existing shape, you probably need to
change every one of those functions.
Anonymous Unions
By the way, C++ and more recent dialects of C support anonymous unions. That is, you can leave the union itself unnamed and access the union members as if they were members of the enclosing structure. If you define the shape structure as:
struct shape {
~~~
union {
circle_part circle;
rectangle_part rectangle;
triangle_part triangle;
}; // union name omitted
};
then you can simplify expressions such as:
s->u.rectangle.height = h;
to just:
s->rectangle.height = h;
My examples don’t use anonymous unions because I tested this code with older C compilers that don’t support them.
Virtual functions provide an alternative to discriminated unions that are safe, efficient, and often more maintainable. I'll discuss virtual functions in this context in future articles.
Dan Saks is president of Saks & Associates, a C/C++ training and consulting company. This article is adapted from an article that appeared earlier this year in EE Times.


