J
Jef Driesen
Hi,
I'm implementing polymorphism with vtables in C. Everything works fine
(see code at the end of this post), but I have some questions.
A minimal sample application would look similar to this:
int
main ()
{
base_t *b = base_new ();
derived_t *d = derived_new ();
foo (b, 1);
bar ((derived_t *) b, 1, 2);
destroy (b);
bar (d, 1, 2);
foo ((base_t *) d, 1);
destroy ((base_t *) d);
return 0;
}
As you can see, calling a base class function requires a manual cast,
because C obviously does no automatic downcasting. That's not unexpected
because unlike a real OOP language like C++, C has no built-in knowledge
about the inheritance tree. (Note that I don't want to use C++ for
portability reasons.)
A possible solution would be to hide the existence of the derived_t
class from the user. Thus all constructors would return a base_t
pointer, and all functions of the derived classes would also accept a
base_t pointer:
base_t *d = derived_new ();
bar (d, 1, 2);
foo (d, 1);
destroy (d);
Thus no more manual downcasts, but now there is the risk of passing a
real base_t object to a function from one of the derived_t classes,
without any warnings or errors from the compiler:
base_t *b = base_new ();
bar (b, 1, 2); /* Ouch! */
Is there some solution for this? Can I catch this somehow at runtime?
For functions that are not called through the vtable (e.g. non-virtual
functions) with classes that are not further subclassed (e.g. a leaf
class in the inheritance tree), I can easily detect this type of errors
by inspecting the vtable pointer. If the vtable pointer is equal to the
vtable pointer for that class, I know the object is of the correct
class. However, for functions that are called through the vtable, that
doesn't work because the vtable has already been dereferenced at that
point and the damage has already been done. And for classes that are
further subclassed, the vtable pointers won't necessary match, while
that shouldn't be considered as an error.
Any suggestions to solve this problem?
Thanks for your time,
Jef
------------------------------------------------------------
#include <stdlib.h>
/*
* The base class
*/
typedef struct base_t base_t;
typedef struct base_vtable_t base_vtable_t;
typedef void (*destroy_func_t) (base_t *object);
typedef int (*foo_func_t) (base_t *object, int a);
struct base_vtable_t
{
destroy_func_t destroy;
foo_func_t foo;
};
struct base_t
{
const base_vtable_t *vtable;
/* ... */
};
static void
base_destroy (base_t *object)
{
free (object);
}
static int
base_foo (base_t *object, int a)
{
return 0;
}
static const base_vtable_t *
base_get_vtable (base_t *object)
{
return object->vtable;
}
void
base_init (base_t *object)
{
static const base_vtable_t vtable =
{
base_destroy,
base_foo
};
object->vtable = &vtable;
}
base_t *
base_new ()
{
base_t *object = malloc (sizeof (base_t));
base_init (object);
return object;
}
int
foo (base_t *object, int a)
{
const base_vtable_t *vtable = object->vtable;
return vtable->foo (object, a);
}
void
destroy (base_t *object)
{
const base_vtable_t *vtable = object->vtable;
return vtable->destroy (object);
}
/*
* The derived class
*/
typedef struct derived_t derived_t;
typedef struct derived_vtable_t derived_vtable_t;
typedef int (*bar_func_t) (derived_t *object, int a, int b);
struct derived_vtable_t
{
base_vtable_t base;
bar_func_t bar;
};
struct derived_t
{
base_t base;
/* ... */
};
static void
derived_destroy (base_t *object)
{
free (object);
}
static int
derived_foo (base_t *object, int a)
{
return a;
}
static int
derived_bar (derived_t *object, int a, int b)
{
return a + b;
}
static const derived_vtable_t *
derived_get_vtable (derived_t *object)
{
return (const derived_vtable_t *) ((base_t *) object)->vtable;
}
void
derived_init (derived_t *object)
{
static const derived_vtable_t vtable =
{
{
derived_destroy,
derived_foo
},
derived_bar
};
base_init (&object->base);
((base_t *) object)->vtable = &vtable.base;
}
derived_t *
derived_new ()
{
derived_t *object = malloc (sizeof (derived_t));
derived_init (object);
return object;
}
int
bar (derived_t *object, int a, int b)
{
const derived_vtable_t *vtable = (const derived_vtable_t *)
((base_t *) object)->vtable;
return vtable->bar (object, a, b);
}
I'm implementing polymorphism with vtables in C. Everything works fine
(see code at the end of this post), but I have some questions.
A minimal sample application would look similar to this:
int
main ()
{
base_t *b = base_new ();
derived_t *d = derived_new ();
foo (b, 1);
bar ((derived_t *) b, 1, 2);
destroy (b);
bar (d, 1, 2);
foo ((base_t *) d, 1);
destroy ((base_t *) d);
return 0;
}
As you can see, calling a base class function requires a manual cast,
because C obviously does no automatic downcasting. That's not unexpected
because unlike a real OOP language like C++, C has no built-in knowledge
about the inheritance tree. (Note that I don't want to use C++ for
portability reasons.)
A possible solution would be to hide the existence of the derived_t
class from the user. Thus all constructors would return a base_t
pointer, and all functions of the derived classes would also accept a
base_t pointer:
base_t *d = derived_new ();
bar (d, 1, 2);
foo (d, 1);
destroy (d);
Thus no more manual downcasts, but now there is the risk of passing a
real base_t object to a function from one of the derived_t classes,
without any warnings or errors from the compiler:
base_t *b = base_new ();
bar (b, 1, 2); /* Ouch! */
Is there some solution for this? Can I catch this somehow at runtime?
For functions that are not called through the vtable (e.g. non-virtual
functions) with classes that are not further subclassed (e.g. a leaf
class in the inheritance tree), I can easily detect this type of errors
by inspecting the vtable pointer. If the vtable pointer is equal to the
vtable pointer for that class, I know the object is of the correct
class. However, for functions that are called through the vtable, that
doesn't work because the vtable has already been dereferenced at that
point and the damage has already been done. And for classes that are
further subclassed, the vtable pointers won't necessary match, while
that shouldn't be considered as an error.
Any suggestions to solve this problem?
Thanks for your time,
Jef
------------------------------------------------------------
#include <stdlib.h>
/*
* The base class
*/
typedef struct base_t base_t;
typedef struct base_vtable_t base_vtable_t;
typedef void (*destroy_func_t) (base_t *object);
typedef int (*foo_func_t) (base_t *object, int a);
struct base_vtable_t
{
destroy_func_t destroy;
foo_func_t foo;
};
struct base_t
{
const base_vtable_t *vtable;
/* ... */
};
static void
base_destroy (base_t *object)
{
free (object);
}
static int
base_foo (base_t *object, int a)
{
return 0;
}
static const base_vtable_t *
base_get_vtable (base_t *object)
{
return object->vtable;
}
void
base_init (base_t *object)
{
static const base_vtable_t vtable =
{
base_destroy,
base_foo
};
object->vtable = &vtable;
}
base_t *
base_new ()
{
base_t *object = malloc (sizeof (base_t));
base_init (object);
return object;
}
int
foo (base_t *object, int a)
{
const base_vtable_t *vtable = object->vtable;
return vtable->foo (object, a);
}
void
destroy (base_t *object)
{
const base_vtable_t *vtable = object->vtable;
return vtable->destroy (object);
}
/*
* The derived class
*/
typedef struct derived_t derived_t;
typedef struct derived_vtable_t derived_vtable_t;
typedef int (*bar_func_t) (derived_t *object, int a, int b);
struct derived_vtable_t
{
base_vtable_t base;
bar_func_t bar;
};
struct derived_t
{
base_t base;
/* ... */
};
static void
derived_destroy (base_t *object)
{
free (object);
}
static int
derived_foo (base_t *object, int a)
{
return a;
}
static int
derived_bar (derived_t *object, int a, int b)
{
return a + b;
}
static const derived_vtable_t *
derived_get_vtable (derived_t *object)
{
return (const derived_vtable_t *) ((base_t *) object)->vtable;
}
void
derived_init (derived_t *object)
{
static const derived_vtable_t vtable =
{
{
derived_destroy,
derived_foo
},
derived_bar
};
base_init (&object->base);
((base_t *) object)->vtable = &vtable.base;
}
derived_t *
derived_new ()
{
derived_t *object = malloc (sizeof (derived_t));
derived_init (object);
return object;
}
int
bar (derived_t *object, int a, int b)
{
const derived_vtable_t *vtable = (const derived_vtable_t *)
((base_t *) object)->vtable;
return vtable->bar (object, a, b);
}