C design style when using typedef structs
When one uses typedef structs within a source file and an external system will need to connect to this class via an interface, what is the correct way to handle the input?
One example is if a source file inside a sub-system has
typedef struct
{
double latitude;
double longitude;
}
GPSLocation;
and an external class wants to use the following function
void setupGPSSystem(char* navigationSystem, GPSLocation *start, GPSLocation *destination)
Lets say I abstract this using the interface and have a function which the external class calls. Should the function take arguments of type GPSLocation(thus forcing the external system to #include
the source file with 开发者_JAVA百科the struct present, not so external now) or is it better to keep all typedef structs used within the sub-system and thus having an interface function as follows?
void setupGPSSystem(char* navigationSystem, double startLat, double startLong, double destinationLat, double destinationLong)
You can declare the types in your interface's public header, and have the structure definitions hidden in your implementation source files. So, you might have:
gps_system.h:
typedef struct GPSLocation GPSLocation;
void setupGPSSystem(char* navigationSystem, GPSLocation *start, GPSLocation *destination);
gps_system.c:
struct GPSLocation
{
double latitude;
double longitude;
}
That way, you get the best of both worlds: your users can use meaningful types in the interface, and your implementation is private.
In designing the module you need to decide whether users of this module need access to the fields within the structure directly, and this will determine where you end up defining the structure. I'll describe a few different scenarios:
1. A user (of the module) will directly manipulate the GPS data within your structure. You have no choice but to define the structure in a header (.h) file as part of the API. The advantage is that this technique is simple and gives the user the ability to statically or dynamically allocate memory for your structure as required (since the structure's make-up is known to your other modules). This disadvantage is that this technique does not hide your data; it can be intentionally or inadvertently corrupted by the user.
2. A user does not care what a "GPS location" is, it will always use the module's functions to operate on your data structure. In this case your structure can be opaque. You declare it in the header (.h) file and define it in the source (.c) file (as covered by Graham's answer). The advantage is you get to hide all of your data which means a user can't easily corrupt it or (in the case of a proprietary library) understand how it has been implemented. The disadvantage is your module must manage the allocation (and freeing) of your opaque type (see point 3 below for an expansion of this idea).
gps_system.h
typedef struct _GPSLocation GPSLocation;
void setupGPSSystem(char* navigationSystem, GPSLocation *start, GPSLocation *destination);
gps_system.c
struct _GPSLocation
{
double latitude;
double longitude;
}
3. You'd like to give the user read-only access to some fields but to hide others. This can be a useful technique for hiding implementation details and to prevent a user from corrupting your data while making it easy to access useful information. The disadvantage is you still need your module to manage allocation of the opaque type.
gps_system.h
typedef struct
{
double latitude;
double longitude;
}
GPSLocation;
/* Create a new GPS object. */
void gps_new(GPSLocation **gps);
/* Set the GPS object's current location. */
void gps_set(GPSLocation *gps, double latitude, double longitude);
/* Calculate the distance from a GPS coordinate to a different location. */
void gps_distance(GPSLocation *gps, double latitude, double longitude);
/* Free all memory allocated to a gps object. */
void gps_delete(GPSLocation *gps);
gps_system.c
struct GPSLocation_private
{
GPSLocation gps_public;
int field_0;
int field_1;
};
/** Convert from the public version of the gps object object to the private. */
#define gps_get_private(gps_public) ((struct GPSLocation_private *)(((char *)(gps_public)) - offsetof(struct GPSLocation_private, gps_public)))
void gps_new(GPSLocation **gps)
{
struct GPSLocation_private *priv;
priv = malloc(sizeof(struct GPSLocation_private));
if (priv)
{
priv->field_0 = 1234;
priv->field_1 = 4567;
priv->gps_public.latitude = 1111;
priv->gps_public.longitude = 2222;
gps = &priv->gps_public;
}
else
{
*gps = NULL;
}
}
void gps_set(GPSLocation *gps, double latitude, double longitude)
{
struct GPSLocation_private *priv;
priv = gps_get_private(gps);
/* Do stuff with 'priv'. */
}
void gps_delete(GPSLocation *gps)
{
struct GPSLocation_private *priv;
priv = gps_get_private(gps);
free(priv);
}
Your subsystem probably (ought to) have a header file defining the function prototypes other people use to access your system (the API). There is nothing wrong with also defining your struct in that header file, complete with typedef. In fact it is very common. That way, providing your function prototype follows that prototype, you're ok. So this:
#ifndef MYSUBSYSTEM_H
#define MYSUBSYSTEM_H
typedef struct
{
double latitude;
double longitude;
}
GPSLocation;
void setupGPSSystem(char* navigationSystem,
GPSLocation *start, GPSLocation *destination);
#endif
Would make a good header. Newline in the middle of your function to make it fit on SO. #include
that in the code that is linking to yours and you're set.
The GPSLocation
type belongs to the API, and should be on a header file (a file ending in .h
), that users of your API will #include
.
You have to make the user #include
a header with the prototypes for your interface functions, so you may as well stick the struct definition in that header.
It might not be such a hot idea to mention "class" in a question about C-language structs :-) Just to avoid any possible confusion with, say, C++.
You ask "Should the function take arguments of type GPSLocation(thus forcing the external system to #include the source file with the struct present, ... "
The function as you've written it does not take arguments of type GPSLocation; it takes arguments of type pointer to (i.e., address of) a GPSLocation struct, a very different and a wonderful thing.
And, very properly and correctly, you're not forcing anybody to #include anything except the header (.h) file that defines GPSLocation. That's because in your function entry (as I understand it) you are expecting pointers to instantiations err, copies of those structs (start and end) as they exist in the calling program. You'll get at the members of each passed-by-reference struct through their addresses/pointers.
This is by far the best way to pass structs in C -- via pointers to the calling program's copies of those structs.
Forget about the second choice you gave. Forever!
精彩评论