If you want to build a system where you can change the hash function at runtime, you might need some kind of interface or inheritance. But if you end up needing a virtual call per byte (or per a small chunk of bytes), you're killing your performance.
And besides, there's so few hash functions you end up needing in practice that a simple switch-case would do the same with less boilerplate and probably give better performance because compiler optimizations can take place (virtual calls tend to prevent many optimization tricks).
I agree with GP... The choice of CRC was a poor example for an article about OO in C. Something related to filesystems or device drivers would be vastly more practical.
Given some of the other comments, I think this ought to be stressed though… OO doesn't strictly require inheritance, or vtables. A fair number of classes in the C++ STL don't use those features, for example.
It's not that the state is a single integer, either, it's that the concept of a state exists; having a separate type (with the functions surrounding that separate type) exist to embody that concept, and can change a function from an ambiguous "what goes in this uint32_t arg?" to a very obvious md5_state_t.
> or even constructors or destructors (apart from zero-initialization).
Just a nit: MD5 requires initialization that is more than just zero-initialization.
> probably give better performance because compiler optimizations can take place (virtual calls tend to prevent many optimization tricks).
This is about the most concrete argument against vtables (but hardly against OO in general) thus far, and I'd still love more explanation: what makes a branch more optimizable than following a pointer?
I can't really think of a more straightforward way to implement CRC32, frankly, especially assuming that maybe you don't want to force the entire buffer to be present. If you did, you could `uint32_t crc32(unsigned char * buffer, size_t len);` — but you can implement that function in terms of the interface in the article, and if you pick up a bit of inlining from an optimizer, I think it would end up being just as efficient, too. (If you don't, you'll end up with some extra pointer dereferences, but again, the streaming interface supports streaming.)
And perhaps to stress what some other commenters are missing: it's not "you should always OO in C"; it's that if you want an interface to compute, while streaming, the CRC32 of a stream — you need to store that state somewhere, and you need to act on that state somehow. That state is an object: it has a setup, and potentially a teardown, and two relevant methods: feed it data, and get the computed hash.