This is error-prone because of the cast. circle_Area could have the wrong parameters, or the wrong function could be used by accident and it will compile.
Conformance wise, it is undefined behavior: a function that takes a Circle * is being called via a pointer to a function that takes a double *.
You really want the circle_Area to have a Shape * argument.
Firstly:
double
shape_Area(Shape *shape)
{
return (shape->interface->Area)(shape); // we lose shape->instance here, just shape
}
We now have a cast here; but that is now new. The same conversion was being done through the function pointer punning, without a cast (formally undefined in ISO C). Now we have well-defined behavior: the shape->instance pointer is really pointing to a Circle; it is valid to convert that pointer back to a Circle *.
So instead of a function pointer cast with undefined behavior, we have a well-defined function pointer treatment, with a correct cast between data pointers.
Another nice thing now is that type-specific implementation like circle_Area has access to the Shape pointer, and can invoke shape operations on it.
Now you can have subtypes of rectangle which have a completely different internal organization, and don't have to reimplement Area; rectangle_Area works for them. E.g. a PNG image could be a kind of rectangle, using rectangle_Area for its Area implementation. It just provides NominalLength and NominalWidth:
double pngImage_NominalLength(Shape *shape)
{
PNGImage *image = (PNGImage *) shape->instance;
return PNGIMage_width(image); // PNGImage is an opaque type from some third party lib.
}
that is not possible, even if you're prepared to modify rectangle_Area somehow to try to make it work. It doesn't have access to the shape container to be able to invoke the abstract shape call; there is no way it can work with PNGImage.
This is error-prone because of the cast. circle_Area could have the wrong parameters, or the wrong function could be used by accident and it will compile.
Conformance wise, it is undefined behavior: a function that takes a Circle * is being called via a pointer to a function that takes a double *.
You really want the circle_Area to have a Shape * argument.
Firstly:
Then: We now have a cast here; but that is now new. The same conversion was being done through the function pointer punning, without a cast (formally undefined in ISO C). Now we have well-defined behavior: the shape->instance pointer is really pointing to a Circle; it is valid to convert that pointer back to a Circle *.Then we have:
So instead of a function pointer cast with undefined behavior, we have a well-defined function pointer treatment, with a correct cast between data pointers.Another nice thing now is that type-specific implementation like circle_Area has access to the Shape pointer, and can invoke shape operations on it.
Now you can have subtypes of rectangle which have a completely different internal organization, and don't have to reimplement Area; rectangle_Area works for them. E.g. a PNG image could be a kind of rectangle, using rectangle_Area for its Area implementation. It just provides NominalLength and NominalWidth: Whereas if rectangle_Area is like this: that is not possible, even if you're prepared to modify rectangle_Area somehow to try to make it work. It doesn't have access to the shape container to be able to invoke the abstract shape call; there is no way it can work with PNGImage.