What is the proper way to implement an abstract data type in C?

In his book Patterns in C, Adam Petersen describes using a pointer to a struct, which is declared in a header file, to create a first-class abstract data-type:

/* Customer.h */
/* A pointer to an incomplete type (hides the implementation details). */
typedef struct Customer* CustomerPtr;
/* Create a Customer and return a handle to it. */
CustomerPtr createCustomer(const char* name, const Address* address);
/* Destroy the given Customer. All handles to it will be invalidated. */
void destroyCustomer(CustomerPtr customer);

The struct and the two functions are defined in Customer.c:

#include ”Customer.h”
#include <stdlib.h>
struct Customer
    const char* name;
    Address address;
    size_t noOfOrders;
    Order orders[42];
CustomerPtr createCustomer(const char* name, const Address* address)
    CustomerPtr customer = malloc(sizeof * customer);
        /* Initialize each field in the customer... */
    return customer;
void destroyCustomer(CustomerPtr customer)
    /* Perform clean-up of the customer internals, if necessary. */

The way I would organize my code is a bit different. The header file would declare the struct itself, not a pointer to it. Here is the way I was taught of coding something similar:

/* Customer.h */
struct Customer;
extern struct Customer* newCustomer(const char* name);

and declarations:

/* Customer.c */
struct Customer {
    char name[CUSTNAMELENGTH +1];
struct Customer* newCustomer(const char* name) {
    struct Customer* newCustomer;
    newCustomer = malloc(sizeof(struct Customer));
    /* initialize customer instance */
    return newCustomer;

What is the practical difference between the two styles? Is there a potential problem with my way that I don’t see? It seems to successfully encapsulate the object, and provide an interface similar to Petersen’s design. When should I use his design rather than what I have been doing?

These are not that different.

Both .h files effectively declare struct Customer as a forward declaration. The first one also defines a pointer type, which the second doesn’t. Some prefer the typedef as they don’t like to write struct and *; I would probably agree.

The extern declaration is good, but not necessary, I believe. Otherwise, the naming createCustomer vs. newCustomer is fairly arbitrary to me.

One could be more critical about the two styles except that the neither is very complete; the second example even less so.

FYI, Both of these have potential errors.

The first malloc‘s only a single pointer instead of a struct (it references struct * customer instead of struct Customer, so it won’t compile either). It also allocates a fixed size of 42 for the orders, which doesn’t make a lot of sense to me; it would probably be better to make it an expandable data structure (although, the astute reader may note that in C/C++ a structure that ends with an array is potentially expandable — still if one were doing that they’d use something like zero as the array size.)

The second assumes a fixed customer name length, which if sufficiently large is wasteful when not needed, and, presumably can still be exceeded. It would probably be better to allocate strings to size, which would avoid both the space waste and the overflow issue.


I prefer your approach: presenting the API context explicitly as a pointer to a structure.

Some people think presenting the context instead as an unspecified “thing” is cleaner, so they use a typedef to hide the structure and the pointer. I cannot say they’re wrong, but my opinion is that, to a C programmer, a pointer to a structure just as abstract as a typedef of unknown type.

There is one small advantage, though, and that’s that the header file is in control of what the context actually is. So, for example, an API might start its life with a context that’s just an integer (“typedef int CustomerContext”) could later change it to a pointer to a structure (“typedef struct Customer *CustomerContext”) without requiring any changes to the code using the API (as long as the user code doesn’t make invalid assumptions about whether the context is a pointer or an integer). But that doesn’t really matter if your API is starting life with a structure already holding the context.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *