Shao Miller
6/5/2011 5:17:00 PM
On 6/5/2011 5:52 AM, daknok wrote:
> Hi,
>
> If I want to do simple reference counting for different structures I
> would go like this:
>
> struct String {
> unsigned references;
> char *cString;
> size_t length;
> };
>
> void StringRelease(struct String *str) {
> str->references--;
> if (str->references == 0) {
> free(str->cString);
> free(str);
> }
> }
>
> struct String *StringRetain(struct String *str) {
> str->references++;
> return str;
> }
>
> Though if I have about ten structs, I have ten different functions to
> increase and decrease the reference count.
Your 'struct String' contains a 'cString' pointer, which implies that
someone has to set that member somehow. If you offer an allocator which
allocates both a 'struct String' as well as the data that 'cString' will
point to, a reference-counting de-allocator that has knowledge of the
resource pointed-to by 'cString' seems nicely symmetrical.
Does one 'struct String' "own" the string that its 'cString' member
points to? Or are users allowed to modify the 'cString' member
themselves and point to, let's say, a string literal? You wouldn't want
them to free a string literal, most likely. :)
If the string is "owned" by the associated 'struct String', then of
course, a nice thing about having an array of some element type is that
you can allocate 'struct String' and the 'char[]' together.
typedef struct String_ String;
struct string_ {
unsigned int references;
size_t current_length;
size_t maximum_length;
char * cString;
char data_[1];
};
Now you can have an allocator that does something like:
String * new_string(size_t maximum_length) {
String * result;
result = malloc(sizeof *result + maximum_length);
if (!result)
return result; /* pass the null pointer value on */
init_string(result, maximum_length, result->data_);
return result;
}
where 'init_string' is something like:
void init_string(String * string, size_t maximum_length, char * cString);
and sets the relevant members (and 'references' to 1, other stuff,
perhaps). Now when your reference-counting de-allocator is ready to
free the 'struct String', its call to 'free' will include deallocating
the trailing 'char[]' that was allocated by the above call to 'malloc'.
This way, the de-allocator doesn't need to free 2 ranges of memory.
> Is it, in some way, possible
> to just provide one function for increasing and one function for
> decreasing the reference count which can be used for all structs with a
> reference count?
>
I think that depends. If your structs contain references to other
objects (which might contain references to other objects, etc.), it
seems that the knowledge for "collapsing" has to go somewhere. If the
struct "owns" all of the resources associated with it, you could:
- Explicitly 'free' each resource when the struct is being de-allocated.
(Knowledge in the struct-specific function.)
- Track all resources with a linked list and walk the list, freeing
items, when the struct is being de-allocated. (Knowledge in the
resource list.)
If a struct doesn't "own" all of the resources associated with it, then
perhaps you extend your reference-counting approach to such resources,
also. But still, the knowledge for what might be referenced by your
struct needs to go somewhere. Perhaps you have a struct-specific,
reference-counting de-allocator which decrements the references for all
of the resources it is referencing. This is a nicely-collapsible chain
of events.
As already suggested by others, if your structures all begin with the
same type of member, you can have a single reference-counting release
function that takes a pointer to it. While this means not having to use
'StringRelease', 'FileRelease', 'UrlRelease', etc. all over the place,
the knowledge of any "sub-resources" still needs to be somewhere;
possibly in a struct-specific function which can be found via a function
pointer.