Descargo de responsabilidad: Soy un novato completo con C, pero he estado jugando con él tratando de imitar algunas características de las clases. Ok, Sé que si quiero ir por ese camino debería aprender C++, pero considere lo siguiente un pequeño experimento.Spicing C con las clases
Schreiner, en el libro Programación orientada a objetos con la norma ANSI-C sugiere una manera de utilizar punteros para obtener características orientación a objetos en C. Debo admitir que sólo he hojeado el libro, pero no me gusta su enfoque demasiado. Básicamente, se utiliza punteros a funciones con el fin de disponer que
func(foo);
da lugar realmente a llamar
foo.methods->func();
donde foo.methods
es una estructura que contiene punteros a funciones. Lo que no me gusta en este enfoque es que uno tiene que tener la función global foo
de todos modos; es decir, los métodos no son espaciados por la clase en la que viven. Mi sensación es que esto pronto llevará al desorden: piense en dos objetos foo
y bar
, ambos con un método func
pero con un número diferente de parámetros.
Así que he intentado conseguir algo más adecuado para mi gusto. Un primer intento es el siguiente (Omito las declaraciones de razones de brevedad)
#include <stdio.h>
//Instances of this struct will be my objects
struct foo {
//Properties
int bar;
//Methods
void (* print)(struct foo self);
void (* printSum)(struct foo self, int delta);
};
//Here is the actual implementation of the methods
static void printFoo(struct foo self) {
printf("This is bar: %d\n", self.bar);
}
static void printSumFoo(struct foo self, int delta) {
printf("This is bar plus delta: %d\n", self.bar + delta);
}
//This is a sort of constructor
struct foo Foo(int bar) {
struct foo foo = {
.bar = bar,
.print = &printFoo,
.printSum = &printSumFoo
};
return foo;
}
//Finally, this is how one calls the methods
void
main(void) {
struct foo foo = Foo(14);
foo.print(foo); // This is bar: 14
foo.printSum(foo, 2); // This is bar plus delta: 16
}
Ésta es unconvenient pero algo funciona. Lo que no me gusta, sin embargo, es que tienes que agregar explícitamente el objeto en sí mismo como primer argumento. Con un poco de trabajo de preprocesador puedo hacerlo un poco mejor:
#include <stdio.h>
#define __(stuff) stuff.method(* stuff.object)
//Instances of this struct will be my objects
struct foo {
//Properties
int bar;
//Methods
//Note: these are now struct themselves
//and they contain a pointer the object...
struct {
void (* method)(struct foo self);
struct foo * object;
} print;
};
//Here is the actual implementation of the methods
static void printFoo(struct foo self) {
printf("This is bar: %d\n", self.bar);
}
//This is a sort of constructor
struct foo Foo(int bar) {
struct foo foo = {
.bar = bar,
//...hence initialization is a little bit different
.print = {
.method = &printFoo,
.object = &foo
}
};
return foo;
}
//Finally, this is how one calls the methods
void
main(void) {
struct foo foo = Foo(14);
//This is long and unconvenient...
foo.print.method(* foo.print.object); // This is bar: 14
//...but it can be shortened by the preprocessor
__(foo.print); // This is bar: 14
}
Esto es lo más lejos que puedo conseguir. El problema aquí es que no funcionará para métodos con argumentos, ya que las macros de preprocesador no pueden tomar una cantidad variable de argumentos. Por supuesto, uno puede definir las macros _0
, _1
y así sucesivamente de acuerdo con la cantidad de argumentos (hasta que uno se cansa), pero este no es un buen enfoque.
¿Hay alguna manera de mejorar esto y dejar que C use una sintaxis más orientada a objetos?
Debo añadir que en realidad Schreiner hace mucho más de lo que dije en su libro, pero creo que la construcción básica no cambia.
me gustaría utilizar un enfoque vtable para las funciones, que es similar a su segunda aproximación, excepto 'print' sería un puntero. – leppie
"ambos tienen un método func" ... func es el nombre de un campo de puntero a función en una estructura: no hay razón para que la función global a la que apunta deba llamarse simplemente "func". Prefijo/postfijo con algo específico de clase y resuelto ese problema. –
Las macros de preprocesador pueden tomar una cantidad variable de argumentos, desde hace 12 años. –