Para ser específico: anulación de funciones con la capacidad de llamar a los métodos anulados de base.
Hay dos piezas para esto. Uno es el código de biblioteca precompilado (1), y el otro es el código de Usuario de la Biblioteca (2). Estoy implementando un ejemplo clásico de Persona y Empleado más pequeño posible aquí.
Apreciará mucho la respuesta de un desarrollador incondicional de C que conoce los conceptos de OOP. Estoy desarrollando el código de la Biblioteca y el Usuario de la Biblioteca, por lo que tengo control en ambos lugares.
1 A) Código de biblioteca (precompilado)
persona.h
typedef struct Person Person;
struct Person {
char* name;
void (*display)(const Person *self);
};
Person* PersonNew(char* name);
person.c
static void display(const Person *self) {
printf("Name: %s\n", self->name);
}
Person* PersonNew(char* name) {
Person* self = malloc(sizeof(Person));
if(self == NULL) return NULL;
self->name = strdup(name);
self->display = display;
return self;
}
1 B) La biblioteca puede recibir estructuras con personas compuestas a través de punteros porque es responsabilidad de la biblioteca imprimir estas estructuras (por lo tanto, la biblioteca no conoce la existencia de la estructura del empleado)
void print(Person *person) {
person->display(person); // problem, can only print name, no company
}
2) El usuario del Código de la Biblioteca
employee.h
typedef struct Employee Employee;
struct Employee {
Person super;
char* company;
void (*display)(const Employee *self);
};
Employee* EmployeeNew(char* name, char* company);
employee.c
static void display(const Employee *self) {
self->super.display(&self->super); // re-use super display
printf("Company: %s\n", self->company);
}
Employee* EmployeeNew(char* name char* company) {
Employee* employee = malloc(sizeof(Employee));
if(employee == NULL) return NULL;
free(employee->super); // I also have memory leak issue.
employee->super = *PersonNew(name); // library method to get struct with functions.
employee->company = strdup(company);
employee->display = display; // it's own version of display
return employee;
}
main.c
Employee *employee = EmployeeNew("John", "Acme");
print(employee); // problem, calls base method, prints name only
// employee->display(); // works fine, but the requirement is somehow enable library code to be able to call employee version of display.
3 respuestas
Una forma rápida y poco sofisticada de hacerlo podría ser algo como lo siguiente.
typedef struct Person Person;
typedef void (*DISPLAYPROC)(const Person* self);
struct Person {
char* name;
DISPLAYPROC display;
};
void ConstructPerson(Person* self, const char* name);
Person* NewPerson(const char* name);
void person_display(const Person* self);
void person_display(const Person* self) {
printf("Name: %s\n", self->name);
}
void ConstructPerson(Person* self, const char* name)
{
self->name = _strdup(name);
self->display = person_display;
}
Person* NewPerson(const char* name) {
Person* self = (Person *)malloc(sizeof(Person));
if (self == NULL) return NULL;
ConstructPerson(self, name);
return self;
}
void print(Person* person) {
person->display(person); // dispatched at runtime to the right 'display'
}
typedef struct Employee Employee;
struct Employee {
Person super;
char* company;
};
void ConstructEmployee(Employee* self, const char* name, const char* company);
Employee* NewEmployee(const char* name, const char* company);
void employee_display(const Employee* self);
void employee_display(const Employee* self) {
person_display((const Person*)self);
printf("Company: %s\n", self->company);
}
void ConstructEmployee(Employee* self, const char* name, const char* company)
{
ConstructPerson(&self->super, name);
self->super.display = (DISPLAYPROC)employee_display;
self->company = _strdup(company);
}
Employee* NewEmployee(const char* name, const char* company) {
Employee* self = (Employee *)malloc(sizeof(Employee));
if (self == NULL) return NULL;
ConstructEmployee(self, name, company);
return self;
}
int main()
{
Person* person = NewPerson("Bob");
print(person); // calls person_display
Employee* employee = NewEmployee("John", "Acme");
print((Person*)employee); // calls employee_display
free(person);
free(employee);
}
Algunas notas
Person
está incrustado como un campo (no un puntero) enEmployee
para que los lanzamientos sean legales entrePerson*
yEmployee*
;las funciones
New/Construct
separan la inicialización de la asignación, que se mencionó en los comentarios anteriores.las funciones
display
se hacen visibles (no estáticas) para que una clase "derivada" pueda llamar a la función "base".
A la izquierda para que el lector complete:
Destruct
funciones emparejadas con lasConstruct
, para que las cadenas asignadas se liberen debidamente;separación de la inicialización de datos de la inicialización "vtable", de modo que, por ejemplo, el puntero
display
no se inicializa dos veces enConstructEmployee
(una vez enConstructPerson
anidado y luego se sobrescribe inmediatamente después en {{X3} }).
Relleno en la función Destruct para la revisión solamente. Destrucción de la clase Derivada primero antes de la clase Base.
employee.c
void EmployeeDestruct(Employee *self) {
free(self->company); // destruct derived first
PersonDestruct(&self->super); // then destruct base
}
person.c
void PersonDestruct(Person *self) {
free(self->name);
// free(self); // removing since it'd fail badly for local stack vars
}
Dado que coloca Person
al comienzo de Employee
, por lo tanto, no hay ningún problema, pero si se muda a otra ubicación, podría generar una excepción.
La estructura Employee
se verá así:
struct Employee {
char* name;
void (*display)(const Person *self);
char* company;
void (*display)(const Employee *self);
};
Por lo tanto, cuando pasa a print
, ya que print
solo acepta la parte superior de Employee
:
struct Employee {
char* name;
void (*display)(const Person *self);
};
La llamada de display
redirigirá a display
de Person
. por lo tanto, solo se imprime name
.
Para anular la visualización de la Persona, cambiemos estas dos estructuras a:
struct Person {
void (*display)(const Person *self);
char* name;
};
struct Employee {
void (*display)(const Employee *self);
Person super;
char* company;
};
Al mover todos los operandos al comienzo de la estructura, funcionará -> pero esto es peligroso.
Nuevas preguntas
c
C es un lenguaje de programación de uso general utilizado para la programación del sistema (SO e integrado), bibliotecas, juegos y multiplataforma. Esta etiqueta debe usarse con preguntas generales sobre el lenguaje C, como se define en el estándar ISO 9899 (la última versión, 9899: 2018, a menos que se especifique lo contrario; también etiquete las solicitudes específicas de la versión con c89, c99, c11, etc.). C es distinto de C ++ y no debe combinarse con la etiqueta C ++ en ausencia de una razón racional.