Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
In C, when implementing functions which work on some data, I can think of at least the following commonly used styles of how to implement them:
Style 1: The simplest one. User calls functions directly and provides pointer to data as argument.
typedef struct
{
int data;
}SomeStruct;
void setData(SomeStruct *s, int value)
{
s->data = value;
}
// construction
SomeStruct s;
// usage
setData(&s, 123);
Style 2: User calls functions through function pointers, but still needs to manually provide pointer to data.
typedef struct SomeStruct SomeStruct;
struct SomeStruct
{
void (*set)(SomeStruct*, int);
int data;
};
void setData(SomeStruct *s, int value)
{
s->data = value;
}
// construction
SomeStruct s;
s.set = setData;
// usage
s.set(&s, 123);
Style 3: User calls functions through function pointers and doesn't need to provide pointer to data manually.
typedef struct SomeStruct SomeStruct;
struct SomeStruct
{
void (*set)(int);
int data;
};
#define MAX_INSTANCES 2
SomeStruct structs[MAX_INSTANCES];
void setData(SomeStruct *s, int value)
{
s->data = value;
}
void setData0(int value)
{
setData(&structs[0], value);
}
void setData1(int value)
{
setData(&structs[1], value);
}
// construction
structs[0].set = setData0;
structs[1].set = setData1;
SomeStruct *s0 = &structs[0];
// usage
s0->set(123);
In style 2 and 3 the data and setData can be hidden from the user, but I omitted this here for the sake of simplicity.
The question is: Are there established names for these styles?
Style 1 is traditional C.
Style 2 is the beginnings of Object-Oriented C. If it has a name, it is "method implementation pattern"
The problem is that you have an improperly named method for Style 2.
void SomeStruct_setData(SomeStruct *s, int value)
{
s->data = value;
}
or
void SomeStruct_data(SomeStruct *s, int value)
{
s->data = value;
}
are a bit better, because now it signals the intention that SomeStruct is being treated like an object.
Fancier approaches that emulate more of an object oriented system exist. They typically provide (in the header)
struct SomeStruct;
and place the private struct within the SomeStruct.c file. This prevents the ability to access the fields directly, forcing all who modify SomeStruct to use the SomeStruct_*(struct SomeStruct*, ...) methods. Often these approaches will use typdefs to present a slightly renamed struct struct SomeSturct_s* as SomeStruct.
Style 3 is one of the many variations that might be called "vtable implementation" in C. It is often a precursor to polymorphism
Remember that C doesn't have a true polymorphic typing system, so the actual types generally get converted to void * in implementation; but, through patterns of all the object struct fields, you can embed the type into to struct (typically done with a uint32_t or enum, sometimes done with more sophisticated type-handling routines).
In any case, eventually you will need a function that changes behavior, and that function is implemented as a function pointer with many different C-style functions providing the behavior(s), and the type assignment / handling choosing the correct functions to assign to the struct to implement the type's behavior. Basically this is a mini-vtable, if you want to translate it into C++ terms.
Some systems start off with switch statements based on the object type, typically read from the struct (in a fixed location field required by every object struct). Eventually they realize they are just a switch statement that passes the parameters to one method based on the base-class type. By using a function pointer and leveraging construction time assignment, the switch statement can be optimized away.
Object Oriented C is a thing, but it is not a standard.
There are many times when one wants to use Object-Oriented C. The maintenance conveniences and clear delineations for testing are typically the drivers that have people choose this approach. That said, the actual system will resemble a lot of "you write the Object-Oriented simulation in C" and you don't often complete all of the features of whatever OO system you are emulating.
To make this easier, there are a number of approaches:
Write a mini-language that expands into C. This was the original approach of C++, until they decided that the benefits of type checking all the way through the compilation phase was worth combining the pre-processing language with the compiler.
Use a pre-built Object Oriented C environment. GNOME has already built their GObject library, and while the syntax and macro choices may take some getting used to, the entire library is complete, robust, and tested. This "C" as the fundamental layer, allows GNOME to be ported to a few more platforms that its traditional competitor KDE, which requires a C++ compiler (available on fewer platforms).
Refactor your code into reusable components, to speed the construction of your less-than-full-featured OO system. By making the components reusable, either through macro expansion or including methods, you will speed up the creation of new objects, and spend less time debugging them to verify they work as objects.
The first steps are very easy to implement (non-dynamic methods, vtables without sophisticated built-in type checking) but it gets harder as you go along (but not too hard). I've built a system (for fun) that even correctly implements "try / catch" semantics with macros that use the setjmp and longjmp C functions to handle thrown objects with polymorphisim, complete with re-throws, re-catches, and re-handlers following a Java-like behavior.
If you like to play with these kinds of toys, I suggest you get good at standing up unit tests in C, using tools like autotools or its equivalent; because, at least in the early stages, you will be finding that the most basic approach won't capture all the behavior needed as you near completion, and having a testing suite will prevent you from breaking your prior work.
Related
I want to create an API in C. My goal is to implement abstractions to access and mutate struct variables that are defined in the API.
API's header file:
#ifndef API_H
#define API_H
struct s_accessor {
struct s* s_ptr;
};
void api_init_func(struct s_accessor *foo);
void api_mutate_func(struct s_accessor *foo, int x);
void api_print_func(struct s_accessor *foo);
#endif
API' implementation file:
#include <stdio.h>
#include "api.h"
struct s {
int internal;
int other_stuff;
};
void api_init_func(struct s_accessor* foo) {
foo->s_ptr = NULL;
}
void api_print_func(struct s_accessor *foo)
{
printf("Value of member 'internal' = %d\n", foo->s_ptr->internal);
}
void api_mutate_func(struct s_accessor *foo, int x)
{
struct s bar;
foo->s_ptr = &bar;
foo->s_ptr->internal = x;
}
Client-side program that uses the API:
#include <stdio.h>
#include "api.h"
int main()
{
struct s_accessor foo;
api_init_func(&foo); // set s_ptr to NULL
api_mutate_func(&foo, 123); // change value of member 'internal' of an instance of struct s
api_print_func(&foo); // print member of struct s
}
I have the following questions regarding my code:
Is there a direct (non-hackish) way to hide the implementation of my API?
Is this the proper way to create abstractions for the client-side to use my API? If not, how can I improve this to make it better?
"Accessor" isn't a good terminology. This term is used in object oriented programming to denote a kind of method.
The structure type struct s_accessor is in fact something called a handle. It contains a pointer to the real object. A handle is a doubly indirect pointer: the application passes around pointers to handles, and the handles contain pointers to the objects.
An old adage says that "any problem in computer science can be solved by adding another layer of indirection", of which handles are a prime example. Handles allow objects to be moved from one address to another or to be replaced. Yet, to the application, the handle address represents the object, and so when the implementation object is relocated or replaced, as far as the application is concerned, it is still the same object.
With a handle we can do things like:
have a vector object that can grow
have OOP objects that can apparently change their class
relocate variable-length objects such as buffers and strings to compact their memory footprint
all without the object changing its memory address and thus identity. Because the handle stays the same when these changes occur, the application does not have to hunt down every copy of the object pointer and replace it with a new one; the handle effectively takes care of that in one place.
In spite of all of that, handles tend to be unusual in C API's, in particular lower-level ones. Given an API that does not use handles, you can whip up handles around it. Even if you think that the users of your object will benefit from handles, it may be good to split up the API into two: an internal one which only deals with s, and the external one with the struct s_handle.
If you're using threads, then handles require careful concurrent programming. So that is to say, even though from the application's point of view, you can change the handle-referenced object, which is convenient, it requires synchronization. Say we have a vector object referenced by a handle. Application code is working with it, so we can't just suddenly replace the vector with a pointer to a different one (in order to resize it). Another thread is just in the middle of working with the original pointer. The operations that access the vector or store values into it through the handle must be synchronized with the replacement operation. Even if all of that is done right, it's going to add a lot of overhead, and so then application people may notice some performance problems and ask for escape hatches in the API, like for some functions function to "pin" down a handle so that the object cannot move while an efficient operation works directly with the s object inside it.
For that reason, I would tend stay away from designing a handle API, and make that sort of thing the application's problem. It may well be easier for a multi-threaded application to just use a well-designed "just the s please" API correctly, than to write a completely thread-safe, robust, efficient struct s_handle layer.
Is there a direct (non-hackish) way to hide the implementation of my API?
Basically the "rule #1" of hiding the implementation of an API in C is not to allow an init operation whereby the client application declares some memory and your API initializes it. That said, it is possible like this:
typedef struct opaque opaque_t;
#ifndef OPAQUE_IMPL
struct opaque {
int dummy[42]; // big enough for all future extension
} opaque_t;
#endif
void opaque_init(opaque_t *o);
In this declaration, we have revealed nothing to the client, other than that our objects are buffers of memory that require int alignment, and are at least 42 int wide.
In actual fact, the objects are smaller; we have just added a reserve amount for future growth. We can make our actual object larger withotu having to re-compile the clients, as long as our object does not require more than int [42] bytes.
Why we have that #ifndef is that the implementation code will do something like this:
#define OPAQUE_IMPL // suppress the fake definition in the header
#include "opaque.h"
// actual definition
struct opaque {
int whatever;
char *name;
};
This kind of thing plays it loose with the "law" of ISO C, because effectively the client and implementation are using a different definition of the struct opaque type.
Allowing clients to allocate the objects themselves yields certain efficiencies, because allocating objects in automatic storage (i.e. declaring them as local variables) can place them in the stack with very little overhead compared to dynamic memory allocation.
The more common approach for opaqueness is not to provide an init operation at all, only an operation for allocating a new object and destroying it:
typedef struct opaque opaque_t; // incomplete struct
opaque_t *opaque_create(/* args .... */);
void opaque_destroy(opaque_t *o);
Now the caller knows nothing, other than that an "opaque" object is represented as a pointer, the same pointer over its entire lifetime.
Total opaqueness may not be worth it for an API which is internal to an application or application framework. It's useful for an API that has external clients, like application developers in a different team or organization.
Ask yourself the question: would the client of this API, and its implementation, ever be shipped and upgraded separately? If the answer is no, then that diminishes the need for total opaqueness.
this is the right way to do abstarction and encapsulation in C applications.
use the Incomplete Types in C Language for hiding structure details. You can define structures, unions, and enumerations without listing their members (or values, in the case of enumerations). Doing so results in an incomplete type. You can't declare variables of incomplete types, but you can work with pointer to those types
constness in c lang
in evrey function espicialy those that you are exposing in api, that do not change the pointer or the structure data pointed by pointer, better and shall be const pointer. this will ensure (somehow :-) you still can change it in c) to the api user that you are not changing structure data. you can also protect the datat and the address by double const the pointer, seee below:
#ifndef API_H
#define API_H
typedef struct s_accessor s_accessor, *p_s_accessor;
void api_init_func(p_s_accessor p_foo);
void api_mutate_func(p_s_accessor p_foo, int x);
void api_print_func(const p_s_accessor const p_foo);
#endif
in the api.c you can complete the structure type:
struct s {
int internal;
int other_stuff;
};
all auxilary functions should be static in api.c(limit the fucntions scope to api.c only!
minimise the includes in the api.h.
regarding question 1 idont think there is a way that you can hide the implementaion details!
I need to create a library in C and I am wondering how to manage objects: returning allocated (ex: fopen, opendir) or in-place initialization (ex: GNU hcreate_r).
I understand that it is mostly a question of taste, and I'm inclined to choose the allocating API because of the convenience when doing lazy initialization (by testing if the object pointer is NULL).
However, after reading Ulrich's paper (PDF), I'm wondering if this design will cause locality of reference problems, especially if I compose objects from others:
struct opaque_composite {
struct objectx *member1;
struct objecty *member2;
struct objectz *member2;
/* ... */
};
Allocation of such an object will make a cascade of other sub-allocations. Is this a problem in practice? And are there other issues that I should be aware of?
The thing to consider is whether the type of the object the function constructs is opaque. An opaque type is only forward-declared in the header file and the only thing you can do with it is having a pointer to it and passing that pointer to separately compiled API functions. FILE in the standard library is such an opaque type. For an opaque type, you have no option but have to provide an allocation and a deallocation function as the user has no other way to obtain a reference to an object of that type.
If the type is not opaque – that is, the definition of the struct is in the header file – it is more versatile to have a function that does only initialization – and, if required, another that does finalization – but no allocation and deallocation. The reason is that with this interface, the user can decide whether to put the objects on the stack…
struct widget w;
widget_init(&w, 42, "lorem ipsum");
// use widget…
widget_fini(&w);
…or on the heap.
struct widget * wp = malloc(sizeof(struct widget));
if (wp == NULL)
exit(1); // or do whatever
widget_init(wp, 42, "lorem ipsum");
// use widget…
widget_fini(wp);
free(wp);
If you think that this is too much typing, you – or your users themselves – can easily provide convenience functions.
inline struct widget *
new_widget(const int n, const char *const s)
{
struct widget wp = malloc(sizeof(struct widget));
if (wp != NULL)
widget_init(wp, n, s);
return wp;
}
inline void
del_widget(struct widget * wp)
{
widget_fini(wp);
free(wp);
}
Going the other way round is not possible.
Interfaces should always provide the essential building blocks to compose higher-level abstractions but not make legitimate uses impossible by being overly restrictive.
Of course, this leaves us with the question when to make a type opaque. A good rule of thumb – that I have first seen in the coding standards for the Linux kernel – might be to make types opaque only if there are no data members your users could meaningfully access. I think this rule should be refined a little to take into account that non-opaque types allow for “member” functions to be provided as inline versions in the header files which might be desirable from a performance point of view. On the other hand, opaque types provide better encapsulation (especially since C has no way to restrict access to a struct's members). I would also lean towards an opaque type more easily if making it not opaque would force me to #include headers into the header file of my library because they provide definitions of the types used as members in my type. (I'm okay with #includeing <stdint.h> for uint32_t. I'm a little less easy about #includeing a large header such as <unistd.h> and I'd certainly try to avoid having to #include a header from a third-party library such as <curses.h>.)
IMO the "cascade of sub-allocations" is not a problem if you keep the object opaque so you can keep it in a consistent state. The creation and destruction routines will have some added complexity dealing with an allocation failure part way through creation, but nothing too onerous.
Besides the option to have a static/stack-allocated copy (which I'm generally not fond of anyway), in my mind, the main advantage of a scheme like:
x = initThang(thangPtr);
is the ease of returning a variety of more specific error codes.
I read plenty of questions regarding
declaration of functions inside structure?
But I did not get a satisfactory answer.
My question is not about whether functions can be declared inside a structure or not?
Instead, my question is
WHY function can not be declared inside structure?
Well that is the fundamental difference between C and C++ (works in C++). C++ supports classes (and in C++ a struct is a special case of a class), and C does not.
In C you would implement the class as a structure with functions that take a this pointer explicitly, which is essentially what C++ does under the hood. Coupled with a sensible naming convention so you know what functions belong to which classes (again something C++ does under then hood with name-mangling), you get close to object-based if not object-oriented programming. For example:
typedef struct temp
{
int a;
} classTemp ;
void classTemp_GetData( classTemp* pThis )
{
printf( "Enter value of a : " );
scanf( "%d", &(pThis->a) );
}
classTemp T ;
int main()
{
classTemp_GetData( &T );
}
However, as you can see without language support for classes, implementing then can become tiresome.
In C, the functions and data structures are more or less bare; the language gives a minimum of support for combining data structures together, and none at all (directly) for including functions with those data structures.
The purpose of C is to have a language that translates as directly as possible into machine code, more like a portable assembly language than a higher-level language such as C++ (not that C++ is all that high-level). C let's you get very close to the machine, getting into details that most languages abstract away; the down side of this is that you have to get close to the machine in C to use the language to its utmost. It takes a completely different approach to programming from C++, something that the surface similarities between them hide.
Check out here for more info (wonderful discussions there).
P.S.: You can also accomplish the functionality by using function pointers, i.e.
have a pointer to a function (as a variable) inside the struct.
For example:
#include <stdio.h>
struct t {
int a;
void (*fun) (int * a); // <-- function pointers
} ;
void get_a (int * a) {
printf (" input : ");
scanf ("%d", a);
}
int main () {
struct t test;
test.a = 0;
printf ("a (before): %d\n", test.a);
test.fun = get_a;
test.fun(&test.a);
printf ("a (after ): %d\n", test.a);
return 0;
}
WHY function can not be declared inside structure?
Because C standard does not allow to declare function/method inside a structure. C is not object-oriented language.
6.7.2.1 Structure and union specifiers:
A structure or union shall not contain a member with incomplete or function type(hence,
a structure shall not contain an instance of itself, but may contain a pointer to an instance
of itself), except that the last member of a structure with more than one named member
may have incomplete array type; such a structure (and any union containing, possibly
recursively, a member that is such a structure) shall not be a member of a structure or an
element of an array.
I suppose there were and are many reasons, here are several of them:
C programming language was created in 1972, and was influenced by pure assembly language, so struct was supposed as "data-only" element
As soon as C is NOT object oriented language - there is actually no sense to define functions inside "data structure", there are no such entity as constructor/method etc
Functions are directly translated to pure assembly push and call instructions and there are no hidden arguments like this
I guess, because it wouldn't make much sence in C. If you declare a function inside structure, you expect it to be somehow related to that structure, right? Say,
struct A {
int foo;
void hello() {
// smth
}
}
Would you expect hello() to have access to foo at least? Because otherwise hello() only got something like namespace, so to call it we would write A.hello() - it would be a static function, in terms of C++ - not much difference from normal C function.
If hello() has access to foo, there must be a this pointer to implement such access, which in C++ always implicitly passed to functions as first argument.
If a structure function has access to structure variables, it must be different somehow from access that have other functions, again, to add some sence to functions inside structures at all. So we have public, private modificators.
Next. You don't have inheritance in C (but you can simulate it), and this is something that adds lots of sence to declaring functions inside structutes. So here we'd like to add virtual and protected.
Now you can add namespaces and classes, and here you are, invented C++ (well, without templates).
But C++ is object-oriented, and C is not. First, people created C, then they wrote tons of programs, understood some improvements that could be made, and then, following reasonings that I mentioned earlier, they came up with C++. They did not change C instead to separate concepts - C is procedure-oriented, and C++ is object-oriented.
C was designed so that it could be processed with a relatively simple compilation system. To allow function definitions to appear within anything else would have required the compiler to keep track of the context in which the function appeared while processing it. Given that members of a structure, union, or enum declaration do not enter scope until the end of the declaration, there would be nothing that a function declared within a structure could do which a function declared elsewhere could not.
Conceptually, it might have been nice to allow constant declarations within a structure; a constant pointer to a literal function would then be a special case of that (even if the function itself had to be declared elsewhere), but that would have required the C compiler to keep track of more information for each structure member--not just its type and its offset, but also whether it was a constant and, if so, what its value should be. Such a thing would not be difficult in today's compilation environments, but early C compilers were expected to run with orders of magnitude less memory than would be available today.
Given that C has been extended to offer many features which could not very well be handled by a compiler running on a 64K (or smaller) system, it might reasonably be argued that it should no longer be bound by such constraints. Indeed, there are some aspect of C's heritage which I would like to lose, such as the integer-promotion rules which require even new integer types to follow the inconsistent rules for old types, rather than allowing the new types to have explicitly specified behavior [e.g. have a wrap32_t which could be converted to any other integer type without a typecast, but when added to any "non-wrap" integer type of any size would yield a wrap32_t]. Being able to define functions within a struct might be nice, but it would be pretty far down on my list of improvements.
Ok this may be a silly question for many of you.
Let me preface this with a list in order of the languages I've learned over the past 10 years.
[by the way, I understand that some of these are scripting languages]
vb 6.0
html
asp
php
css
javascript
managed c++
c#
c++
C
ASM
Yeah I know I started at the complete opposite end, but hopefully the list keeps me from getting criticized to much with this one heh.
QUESTION: Are there classes in plain old C...I know there are structures...
Also I would like to know if you can declare functions in C structures/classes(if they exist).
I think the answer to both is no, but It's hard to find information on plain C on the internet as most things are for C++. Also, I am interested in knowing any tips, tricks, or warning for working with C. Much thanks in advance.
BTW: I'm interested in C for the purpose of portability and speed.
Classes in C are most often simulated by structs combined with function pointers. Non-virtual functions can be passed alongside a pointer to the struct, like so:
int obj_compare_funct(Obj *a, Obj *b);
int result = compare_two_objects(obj1, obj2, obj_compare_func);
But the real fun starts when you embed the pointers in the struct; this means objects of the same overall "class" can have different "methods". The biggest syntactic downside is that the function pointed to does not automatically know for which object it is being called. So the object needs to be passed as well, which makes for a bit more typing than is normally desirable. For instance:
/***** In the animal.h header file. *****/
typedef struct Animal {
char *name;
void (* speak)(Animal *this); /* The speak "method" */
} Animal;
/* Constructors for various animal types. Implementation detail: set the animal's speak method to the appropriate one for that animal type. */
extern Animal *make_feline(char *name);
extern Animal *make_rodent(char *name);
/***** Somewhere in zoo.c, which #includes animal.h. *****/
Animal *cat = make_feline("Tom");
Animal *mouse = make_rodent("Jerry");
cat->speak(cat); /* Print "Tom says meow!" */
mouse->speak(mouse); /* Print "Jerry says squeak!" */
This example is a bit looser than the inheritance model provided by languages such as Java -- an Animal instance can have any behaviour at all, rather than one of a specific set of behaviours depending on its subclass. To make things a bit stricter, the methods are usually combined into a struct called a vtable (virtual function table). One vtable is pre-made for each subtype, and the appropriate one pointed to from the instance.
Note that none of this directly helps you have different fields for each subtype -- that's trickier (especially syntactically) and can be done either by the trick of casting an object to its first member, e.g.:
/* Can be treated as an Animal if you cast its pointer. */
typedef struct Cat { Animal super; int num_fleas; } Cat;
Or using opaque pointers, e.g.
typedef struct Animal { char *name; void *species_specific_data; } Animal;
Where the extra fields would be hidden away behind that void * pointer, and accessible through the methods particular to that species.
C doesn't have classes. That was one reason for the creation of C++, besides function overloading, operator overloading, and templates.
Of course, code acting class-like was sometimes written long before the existence of C++:
typedef struct class1 class1;
struct class1 {
int (*constructor) (class1 *this);
int (*destructor) (class1 *this);
...
};
C has no classes, and structs in C cannot have functions within them.
There are many differences between C and C++ structs, and yes C doesn't have classes.
For example consider the following code:
struct demo
{
int a;//public by default
void set()// valid in C++, invalid in C
{
//do something with a
}
};
Other differences between C and C++ structs are:
->Constructors and destructors for object initialization and destruction purpose.
->Support of operator overloading, templates,exception handling mechanism etc in C++ structs.
Properties shared between structs in C and POD-structs in C++:
1)Data members are allocated so that later members have higher addresses within an object, except where separated by an access-specifier.
2)Two POD-struct types are layout-compatible if they have the same number of nonstatic data members, and corresponding nonstatic data members (in order) have layout-compatible types.
A POD-struct may contain unnamed padding.
3)A pointer to a POD-struct object, suitably converted using a reinterpret cast, points to its initial member and vice versa, implying that there is no padding at the beginning of a POD-struct.
4)A POD-struct may be used with the offsetof macro.
Source -wiki
Remember that C was created in 1972, well before the ideas of object-oriented programming was common. At the time, object-oriented programming was limited to very few languages, like Simula 67.
So, no, C does not have classes.
Coming from OO (C#, Java, Scala) I value very highly the principles of both code reuse and type-safety. Type arguments in the above languages do the job and enable generic data structures which are both type-safe and don't 'waste' code.
As I get stuck into C, I'm aware that I have to make a compromise and I'd like it to be the right one. Either my data structures have a void * in each node / element and I lose type safety or I have to re-write my structures and code for each type I want to use them with.
The complexity of the code is an obvious factor: iterating through an array or a linked-list is trivial and adding a *next to a struct is no extra effort; in these cases it makes sense not to try and re-use structures and code. But for more complicated structures the answer isn't so obvious.
There's also modularity and testability: separating out the type and its operations from the code that uses the structure makes testing it easier. The inverse is also true: testing the iteration of some code over a structure whilst it's trying to do other things gets messy.
So what's your advice? void * and reuse or type-safety and duplicated code? Are there any general principles? Am I trying to force OO onto procedural when it won't fit?
Edit: Please don't recommend C++, my question is about C!
I would say use void * so you can re-use the code. It's more work to re-implement e.g. a linked list, than to make sure you get/set the data in the list properly.
Take as many hints from glib as possible, I find their data structures very nice and easy to use, and have had little trouble because of the loss of type safety.
I think you'll have to strike a balance between the two, just as you suggest. If the code is only a few lines and trivial I would duplicate it but if it's more complex, I would consider working with void* to avoid having to do any potential bug fixing and maintenance in several places and also to reduce the code size.
If you look at the C runtime library, there's several "generic" functions that work with void*, one common example is sorting with qsort. It would be madness to duplicate this code for every type you'd like to sort.
There's nothing wrong with using void pointers. You don't even have to cast them when assigning them to a variable of type of pointer since the conversion is done internally. It migtht be worth having a look at this: http://www.cpax.org.uk/prg/writings/casting.php
The answer this question is the same as getting efficient templates for link list in C++.
a) Create an abstract version of the algorithm that uses void* or some Abstracted Type
b) Create a light weight public interface to call the Abstracted Type algorithms and caste between them.
For example.
typedef struct simple_list
{
struct simple_list* next;
} SimpleList;
void add_to_list( SimpleList* listTop, SimpleList* element );
SimpleList* get_from_top( SimpleList* listTop );
// the rest
#define ListType(x) \
void add_ ## x ( x* l, x* e ) \
{ add_to_list( (SimpleList*)l, (SimpleList*)x ); } \
void get_ ## x ( x* l, x* e ) \
{ return (x*) get_from_to( (SimpleList*)l ); } \
/* the rest */
typedef struct my_struct
{
struct my_struct* next;
/* rest of my stuff */
} MyStruct;
ListType(MyStruct)
MyStruct a;
MyStruct b;
add_MyStruct( &a, &b );
MyStruct* c = get_MyStruct(&a);
etc etc.
We use OO in C a lot here, but only for encapsulation and abstraction, no polymorphism or so.
Which means we have specific types, like FooBar(Foo a, ...) but, for our collection "classes", we use void *. Just use void * where multiple types could be used, BUT, by doing so, ensure you don't need the argument to be of a specific type. As per collection, having void * is alright, because the collection doesn't care about the type. But if your function can accept type a and type b but none other, make two variants, one for a and one for b.
The main point is to use a void * only when you don't care about the type.
Now, if you have 50 types with the same base structure (let's say, int a; int b; as first members of all types), and want a function to act upon those types, just make the common first members a type by itself, then make the function accept this, and pass object->ab or (AB*)object is your type is opaque, both will work if ab is the first field in your struct.
You can use macros, they will work with any type and the compiler will check statically the expanded code. The downside is that the code density (in the binary) will worsen and they are more difficult to debug.
I asked this question about generic functions some time ago and the answers could help you.
You can efficiently add type information, inheritance and polymorphism to C data structures, that's what C++ does. (http://www.embedded.com/97/fe29712.htm)
Definitely generic void*, never duplicate code!
Take into account that this dilemma was considered by many a C programmer, and many major C projects. All serious C projects I've ever encountered, whether open-source or commercial, picked the generic void*. When used carefully and wrapped into a good API, it is barely a burden on the user of the library. Moreover, void* is idiomatic C, recommended directly in K&R2. It is the way people expect code to be written, and anything else would be surprising and badly accepted.
You can build a (sort of) OO framework using C, but you miss out on a lot of the benefits ... like an OO type system that the compiler understands. If you insist on doing OO in a C-like language, C++ is a better choice. It is more complicated than vanilla C, but at least you get proper linguistic support for OO.
EDIT: Ok ... if you insist that we don't recommend C++, I recommend that you don't do OO in C. Happy? As far as your OO habits are concerned, you should probably think in terms of "objects", but leave inheritance and polymorphism out of your implementation strategy. Genericity (using function pointers) should be used sparingly.
EDIT 2: Actually, I think that use of void * in a generic C list is reasonable. It is just trying to build an mock OO framework using macros, function pointers, dispatching and that kind of nonsense that I think is a bad idea.
In Java all collections from java.util package in effect hold equivalent of void* pointer ( the Object ).
Yes, generics ( introduced in 1.5 ) add syntactic sugar and prevent you from coding unsafe assignments, however the storage type remains Object.
So, I think there is no OO crime commited when you use void* for generic framework type.
I would also add type-specific inlines or macro wrappers that assign/retrieve data from the generic structures if you do this often in your code.
P.S. The one thing that you should NOT do is to use void** to return allocated/reallocated generic types. If you check the signatures of malloc/realloc you will see that you can achieve correct memory allocations without dreaded void** pointer. I am only telling this because I've seen this in some open-source project, that I do not wish to name here.
A generic container can be wrapped with a little work so that it can be instantiated in type-safe versions. Here is an example, full headers linked below:
/* generic implementation */
struct deque *deque_next(struct deque *dq);
void *deque_value(const struct deque *dq);
/* Prepend a node carrying `value` to the deque `dq` which may
* be NULL, in which case a new deque is created.
* O(1)
*/
void deque_prepend(struct deque **dq, void *value);
From the header that can be used to instantiate specific wrapped types of deque
#include "deque.h"
#ifndef DEQUE_TAG
#error "Must define DEQUE_TAG to use this header file"
#ifndef DEQUE_VALUE_TYPE
#error "Must define DEQUE_VALUE_TYPE to use this header file"
#endif
#else
#define DEQUE_GEN_PASTE_(x,y) x ## y
#define DEQUE_GEN_PASTE(x,y) DEQUE_GEN_PASTE_(x,y)
#define DQTAG(suffix) DEQUE_GEN_PASTE(DEQUE_TAG,suffix)
#define DQVALUE DEQUE_VALUE_TYPE
#define DQREF DQTAG(_ref_t)
typedef struct {
deque_t *dq;
} DQREF;
static inline DQREF DQTAG(_next) (DQREF ref) {
return (DQREF){deque_next(ref.dq)};
}
static inline DQVALUE DQTAG(_value) (DQREF ref) {
return deque_value(ref.dq);
}
static inline void DQTAG(_prepend) (DQREF *ref, DQVALUE val) {
deque_prepend(&ref->dq, val);
}
deque.h: http://ideone.com/eDNBN
deque_gen.h: http://ideone.com/IkJRq