[Continued from Uses of discriminated unions (Part 1)]
A union paired with a discrete value that indicates the active member of the union is called a discriminated union or a tagged union. Some programming languages have a similar construct called a variant record. The discrete value is called a discriminator or tag. I prefer the terms discriminated union and discriminator because tag already has another meaning in C. The discriminator typically has an enumeration type, but could have an integral type.
You can write a small assortment of initialization functions to properly initialize each kind of shape. For example:
void rectangle_construct
(shape *s, double h, double w) {
s->kind = sk_rectangle;
s->u.rectangle.height = h;
s->u.rectangle.width = w;
}
initializes a shape as a rectangle with a particular height and width. You can write similar initialization functions for circle and triangles. Using these functions, you can create an array of assorted shapes as follows:
shape sa[4];
~~~
circle_construct(&sa[0], 2);
triangle_construct(&sa[1], 5, 6, asin(0.8));
rectangle_construct(&sa[2], 3, 4);
circle_construct(&sa[3], 3);
Suppose your application needs to determine the shape with the largest area in a collection of shapes. You can do the job with the function named largest shown in Listing 1.
Listing 1: A function that finds the largest shape in an array of shapes, using an overt switch statement.
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 = -1;
switch (s.kind) {
case sk_circle:
area = PI * s.u.circle.radius
* s.u.circle.radius;
break;
case sk_rectangle:
area = s.u.rectangle.height
* s.u.rectangle.width;
break;
case sk_triangle:
area = sin(s.u.triangle.angle)
* s.u.triangle.side1
* s.u.triangle.side2 / 2;
break;
}
if (area > max) {
max = area;
p = &s;
}
}
return p;
}
Most of the loop body in Listing 1 is a switch statement that computes the area of a shape. This is arguably a fundamental shape operation that would be better packaged as an operation all by itself:
double shape_area(shape const *s) {
switch (s->kind) {
case sk_circle:
return PI * s->u.circle.radius
* s->u.circle.radius;
case sk_rectangle:
return s->u.rectangle.height
* s->u.rectangle.width;
case sk_triangle:
return sin(s->u.triangle.angle)
* s->u.triangle.side1
* s->u.triangle.side2 / 2;
}
return -1;
}
Using the shape_area function dramatically simplifies the largest function, as shown in Listing 2.
Listing 2: 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);
if (area > max) {
max = area;
p = &s;
}
}
return p;
}
Correctness and maintenance problems
One of the problems with discriminated unions is that it requires 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.3, 4
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 have to change every one of those functions.
Virtual functions provide an alternative to discriminated unions that are safe, efficient, and often more maintainable. I'll discuss virtual functions in my upcoming columns.
Endnotes:
1. Saks, Dan. "Learn about new with placement," Eetindia.co.in, September 2011. http://forum.eetindia.co.in/BLOG_ARTICLE_9401.HTM
2. Saks, Dan. "Employ member new to map devices," Eetindia.co.in, November 2011. http://forum.eetindia.co.in/BLOG_ARTICLE_10437.HTM.
3. Alexandrescu, Andrei. "Discriminated Unions (I)," Dr. Dobbs Journal, April 1, 2002. http://drdobbs.com/184403821.
4. Alexandrescu, Andrei."Discriminated Unions (II)," Dr. Dobbs Journal, June 1, 2002. http://drdobbs.com/184403828.
文章评论(0条评论)
登录后参与讨论