Jef Driesen wrote:
> I'm writing a library (to communicate with a number of devices over a
> serial port) and have some questions about the design. I have now a
> header and source file like this:
>
> /* device.h */
> typedef struct device device;
>
> int device_open (device **dev, const char *name);
> int device_close (device *dev);
> int device_read (device *dev, void *data, unsigned int size);
> int device_write (device *dev, const void *data, unsigned int size);
> [...]
> 1. Is there any advantage to change the typedef to
>
> typedef struct device *device;
>
> and thus hiding the fact that the "device" type is actually a pointer.
"Moving the star" from the parameter lists to the typedef
is perhaps an improvement, but a pretty small one. I think a
better course might be to leave the typedef as it is, and use
`device *device_open(const char *name)' with the convention that
NULL is returned on a failure. This idiom will be familiar to
all C programmers, following the existing model of fopen() and
the like -- but in order to follow it, you need to reveal the
pointer-nature of the returned value.
Keep in mind that hiding implementation details is a means,
not an end in itself. When you're wondering whether to hide
something, think about why you want to keep it hidden.
> 2. I want to add support for a second type of device, which happens to
> be very similar to the first one. For instance the read/write functions
> are different, but the open/close functions and the contents of the
> struct itself are the same. How can I implement this without copying the
> code to a new pair of header and source files?
You could maintain a type indicator in the `struct device'.
One particularly flexible indicator is a set of function pointers:
struct device {
...
int (*read)(device*, void*, unsigned int);
int (*write)(device*, void*, unsigned int);
...
};
If there are a lot of such pointers, you might want to use one
more level of indirection:
struct funcs {
int (*read)(device*, void*, unsigned int);
int (*write)(device*, void*, unsigned int);
int (*scribble)(device*, void*, unsigned int);
int (*doodle)(device*, void*, unsigned int);
int (*spray)(device*, PaintColor);
};
static const struct funcs type1 = {
read1, write1, scribble1, doodle1, sprayany };
static const struct funcs type2 = {
read2, write2, scribble2, doodle2, sprayany };
struct device {
...
const struct funcs *funcs; /* to type1 or type2 */
...
};
As an aside, why use `unsigned int' for what appear to be
counts, instead of `size_t'? That's what it's for, after all.
--
Eric Sosman
lid