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.
c
2
AppDeveloper 28 abr. 2020 a las 12:42

3 respuestas

La mejor respuesta

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) en Employee para que los lanzamientos sean legales entre Person* y Employee*;

  • 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 las Construct, 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 en ConstructEmployee (una vez en ConstructPerson anidado y luego se sobrescribe inmediatamente después en {{X3} }).

1
AppDeveloper 30 abr. 2020 a las 01:42

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
}
0
AppDeveloper 29 abr. 2020 a las 07:36

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.

2
sdao 29 abr. 2020 a las 05:37