p
is being assigned to the return value ofclass->ctor
which is declared asvoid *
so it's a void pointer. Looking at the definition ofString_ctor
you can see this returnsself
which is aString *
, so in this case you're getting avoid *
which can safely be cast to aString *
This is achieved with
*(const struct Class **)p = class;
. Sinceclass
is the first member ofString
, a pointer to aString
will have the same address as a pointer to itsclass
field. So when you convertp
to aClass **
and write to it, you're writing to itsclass
field.
Constructor and Destructor in C
-
20-07-2023 - |
Question
I was reading a book OOC by A.T. Schreiner
, and I'm stuck at the following line in this code:
struct Class {
size_t size;
void *(* ctor) (void *self, va_list *app);
};
struct String {
const void *class; // must be first
char *text;
};
void *new(const void *_class, ...) {
const struct Class *class = _class; // assign the address of `struct String` class
void *p = calloc(1, class->size); // allocate the sizeof(struct String);
assert(p);
*(const struct Class **)p = class; // Force the conversion of p and set the argument `class` as the value of this pointer.
if(class->ctor) {
va_list ap;
va_start(ap, _class);
p = class->ctor(p, &ap); // Now what is `p` here, a `struct String` or `struct Class`.
// and if it is `struct Class` then how it convert to `struct String` in `String_ctor` function
// given below.
va_end(ap);
}
return p;
}
static void *String_ctor(void *_self, va_list *app) {
struct String *self = _self;
const char *text = va_arg(*app, const char *);
self->text = malloc(strlen(text) + 1);
assert(self->text);
strcpy(self->text, text);
return self;
}
// Initialization
static const struct Class _String = {
sizeof(struct String),
String_ctor
};
const void *String = &_String;
// Call like this:
int main(void) {
void *a = new(String, "some text");
}
Now, as you can see, in the new
function, the following line p = class->ctor(p, &ap)
confused me a lot. You can see the comment described above.
Also, I want to know how const void *class
of struct String
is initialized by new
function as the book said.
Solution
OTHER TIPS
p
is neither struct String
nor struct Class
. It's a pointer. It points to a block of memory. How that memory is interpreted is an interesting question.
Assuming we pass the address of a struct Class
object that implements a string, the first calloc
call allocates a chunk of memory the size of struct String
. So it's entirely reasonable to assume p
is a pointer to struct String
.
The new
function knows little about struct String
or any other "class". Almost nothing really, except the fact that the first element of any such struct
is a pointer to a struct Class
object. Since a pointer to any struct
is the same as the pointer to its first element (modulo type), this makes p
also a pointer to a pointer to struct Class
. So this line:
*(const struct Class **)p = class;
assigns the proper value to that pointer-to-struct-Class which p
is pointing to.
Now this line
p = class->ctor(p, &ap);
calls the constructor, passing the "self" pointer to it as in any OO environment. It also assigns the result of the constructor to p
again, presumably because the ctor can (but doesn't have to) reallocate the object.
To recap: p
is a pointer to struct String
that new
interprets as a pointer to the first element of struct String
, which must be the pointer to struct Class
.