Полиморфизм переопределения и базы звонков, имитирующие сотрудника


Я много училась и работала на эту тему в течение месяца или двух, чтобы приблизиться к понятию быть экологически чистыми, т. е. без виртуальных таблиц, а не куча функций с префиксами и т. д. и самое главное функции должны быть определены в пределах структуры как указатели, чтобы имитировать как можно больше классов ООП.

Только конструкторы с префиксами должны быть общедоступными. Я также хотел бы иметь возможность назвать переопределить супер способ.

Я не уверен, если следующий пример-во время компиляции или во время выполнения полиморфизм, но это удовлетворение во время выполнения ее аспекты, но разве это не то, что нам нужно, без необходимости, чтобы взглянуть на тип объекта? Пожалуйста, просветите.

Я высказываюсь за критический анализ и пересмотр или проблема с подходом.

Идея в том, чтобы представить указатель на родительский тип, который хранит ссылку на оригинальные методы, суб структуры могут переопределить реализацию родительских методов, но призыв к оригинальные методы доступны через базовый указатель.

главная.с

int main() {
    Person* person = Person_new("John", "Doe");
    Employee* employee = Employee_new("Jane", "Doe", "Acme", 40000);
    person->display(person); // displaying Person object
    puts("------");
    employee->super.display(&employee->super); // displaying employee info
    // ((Person *)employee)->display(employee); // or this
    person->release(person);
    employee->super.release(&employee->super);
    return 0;
}

человек.ч

typedef struct Person Person;
struct Person {
    Person *base; // to keep access to original funcs in case of overrides
    char* first;
    char* last;
    void (*display)(const Person *self);
    void (*release)(Person *self);
};
Person* Person_new(char* first, char* last);

человек.с

static void display(const Person *self) {
    printf("First: %s\n", self->first);
    printf("Last: %s\n", self->last);
}

static void release(Person *self) {
    free(self);
}

Person* Person_new(char* first, char* last) {
    Person* self = malloc(sizeof *self);
    if(self == NULL) return NULL;

    self->first = strdup(first);
    self->last = strdup(last);
    self->display = display;
    self->release = release;

    self->base = self; // assigning reference to self to retain original functions in case they get overridden

    return self;
}

работник.ч

typedef struct Employee Employee;
struct Employee {
    Person super;
    char* company;
    int salary;
};
Employee* Employee_new(char* first, char* last, char* company, int salary);

работник.с

static void display(const Person* self) {
    self->base->display(self->base); // calling super class func

    printf("Company: %s\n", ((Employee *)self)->company);
    printf("Salary: %d\n", ((Employee*)self)->salary);
}

static void release(Person *self) {
    self->base->release(self->base); // calling super class func
    free(self);
}

Employee* Employee_new(char* first, char* last, char* company, int salary) {
    Employee* employee = malloc(sizeof(Employee));
    if(employee == NULL) return NULL;

    employee->super = *Person_new(first, last);
    employee->super.display = display; // override
    employee->super.release = release; // override

    employee->company = strdup(company);
    employee->salary = salary;

    return employee;
}
First: John
Last: Doe
---
First: Jane
Last: Doe
Company: Acme
Salary: 40000

Редактировать

Дискуссия с Пабло подтвердил, что это правильный подход. Я все еще ищу некоторые дискуссии о том, является ли такой подход подпадает под время компиляции или во время выполнения полиморфизм.



674
5
задан 5 марта 2018 в 12:03 Источник Поделиться
Комментарии
2 ответа

Есть одна вещь, я не о Employee: вы не утечка памяти.

Person super; должно быть указателем, а не объектом. Вы создаете
объект с mallocделает

    employee->super = *Person_new(first, last);

вы не утечка памяти, потому что вы теряете ссылку на указатель, возвращенный
по malloc и вы не можете освободить его, когда вы уничтожить объект. И вы не
проверка malloc возвращается 0. Так что я бы изменить его на:

typedef struct Employee Employee;
struct Employee {
Person super;
char* company;
int salary;
Person *super_ref;
};

Person* Employee_new(char* first, char* last, char* company, int salary) {
Employee* employee = malloc(sizeof(Employee));
if(employee == NULL)
return NULL;

employee->super_ref = *Person_new(first, last);
if(employee->super_ref == NULL)
{
free(employee);
return NULL;
}

employee->super = *employee->super_ref;
employee->super.display = display; // override

employee->company = company;
employee->salary = salary;

return (Person *)employee;
}

так что теперь уничтожить методом это можно сделать free(obj->super_ref); чтобы освободить память
базовый объект.

редактировать

Альтернатива заключается в том, что вы создаете инициализации функции Person объекты, которые получает
объект в качестве аргумента и не выделяет памяти сам по себе. Я бы сделал:

int Person_init(Person *self, char* first, char* last))
{
if(self == NULL || first == NULL || last == NULL)
return NULL;

self->first = first;
self->last = last;
self->display = display;

self->base = self; // assigning reference to self to retain original functions in case they get overriden

return 1;
}

Person* Person_new(char* first, char* last) {
Person* self = malloc(sizeof *self);

if(self == NULL)
return NULL;

if(Person_init(self, first, last) == 0)
{
free(self);
return NULL;
}

return self;
}

и для Employee класс

Person* Employee_new(char* first, char* last, char* company, int salary) {
if(first == NULL || last == NULL || company == NULL)
return NULL;

Employee* employee = malloc(sizeof *employee);
if(employee == NULL)
return NULL;

if(Person_init(&employee->super, first, last) == 0)
{
free(employee);
return NULL;
}
employee->super.display = display; // override

employee->company = company;
employee->salary = salary;

return (Person *)employee;
}

Тогда вам не придется беспокоиться об освобождении объекта базы.


edit2

*ОП сказал в комментарии**


Мне интересно, что случилось с освобождая базовый указатель, я его сразу на Новый free(&employee->super); employee->super = *super;

К сожалению, это, мне кажется, у вас нет реального понимания управление памятью и
что * разыменования оператор делает.

Во-первых, у вас нет гарантии, что после free(ptr) вы можете получить доступ к
Содержание ptr. После free, ptr указывает на несуществующий каталог и
доступ/разыменование это неопределенное поведение.

Во-вторых

employee->super = *Person_new(first, last);

такой же, как делаешь

Person *tmp = Person_new(first, last);
employee->super = *tmp;

*tmp это разыменование tmpэтот тип Person не Person*. В C, когда вы
делать работы со структурой объекты (не указатели) вы копируете по крупицам
битовый шаблон на новый объект. Это означает, что битовый шаблон из
employee->super совпадает с битовым шаблоном *tmpно они не
объектом Сэм, проживают в разных местах в памяти.

Вы можете только позвонить free(ptr) когда ptr магазины адреса возвращаются либо
malloc или realloc. Если у вас free(&employee->super) вы не проезжает
совершенно разные адреса, которые malloc вернулся в Person_new,
это неопределенное поведение, вы не можете сделать это.

Нет другого пути для вас, вы должны сохранить исходный указатель в
структура, это причина, почему я добавил Person *super_ref в struct, так
что и исходный указатель не пропадает.


У нас главная проблема, это реализация вызывает рекурсию в

Я сделал небольшие изменения в ваш код, у меня нет этой проблемы:

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>

typedef struct Person Person;
typedef struct Person {
Person *base; // reference to itself to retain reference to original function
char* first;
char* last;
void (*display)(const Person *self);
void (*destroy)(Person *self);
} Person;
Person* Person_new(char* first, char* last);

static void display(const Person *self) {
printf("First: %s\n", self->first);
printf("Last: %s\n", self->last);
}

static void destroy_person(Person *self)
{
if(self == NULL)
return;

free(self);
}

Person* Person_new(char* first, char* last) {
Person* self = malloc(sizeof(Person));
self->first = first;
self->last = last;
self->display = display;
self->destroy = destroy_person;

self->base = self; // assigning reference to self to retain original functions in case they get overriden

return self;
}

typedef struct Employee Employee;
struct Employee {
Person super;
char* company;
int salary;
};
Person* Employee_new(char* first, char* last, char* company, int salary);

static void display2(const Person* self) {
self->base->display(self->base);

Employee *e = (Employee*) self;

printf("Company: %s\n", e->company);
printf("Salary: %d\n", e->salary);
}

static void destroy_employee(Person *self)
{
if(self == NULL)
return;

if(self->base)
self->base->destroy(self->base);

free(self);
}

Person* Employee_new(char* first, char* last, char* company, int salary) {
Employee* employee = malloc(sizeof(Employee));

employee->super = *Person_new(first, last);
employee->super.display = display2; // override
employee->super.destroy = destroy_employee; // override

employee->company = company;
employee->salary = salary;

return (Person*) employee;
}

int main(void)
{
Person* person = Person_new("John", "Doe");
Person* employee = Employee_new("Jane", "Doe", "Acme", 40000);
person->display(person);
puts("---");
employee->display(employee);

person->destroy(person);
employee->destroy(employee);

}

Он печатает

$ ./b 
First: John
Last: Doe
---
First: Jane
Last: Doe
Company: Acme
Salary: 40000

edit3

Тот факт, что мои небольшие изменения не имеют проблему бесконечной рекурсии в
display2 держали меня думал об этом и я просматриваю код с
отладчик, чтобы посмотреть, почему у меня не было также проблемой. И теперь я понимаю, почему
мой код не имеет этой проблемы, и не стоило это. В
факт, я понял, что есть способ правильно освободить память без
есть отдельный член, как super_ref.

Позвольте мне объяснить:

В Employee_new вы:

employee->super = *Person_new(first, last);
employee->super.display = display2; // override

Я вам сказал, что вас будет утечка памяти, потому что вы потеряете оригинал
указатель malloc возвращает. И это правда, однако мне не удалось реализовать
что вы на самом деле указатель, указывающий на исходный адрес malloc
возвращает: base член Person структура. В Person_new вас

self->base = self;

и это решает проблему с перегрузкой и освобождение, хотя мы
как не понимал этого в то время.

Итак, еще раз, когда вы делаете

employee->super = *Person_new(first, last);

вы разыменования указателя, возвращаемого malloc и сделав копию
объекта в другой объект. Но мне не удалось понять, что
employee->super->base очки на прежнее место возвращается malloc.
Поэтому, когда вы делаете

employee->super.display = display2; // override

вы действительно установив новое значение employee->super.displayно ведь
employee->super это просто копия, оригинал не изменится и с
employee->super->base вы получаете оригинальный объект.

Вот почему

static void display2(const Person* self) {
self->base->display(self->base);
...
}

не должна закончиться в рекурсию, потому что self->display указывает на display2,
но self->base->display по-прежнему неизменной и указывает на display.

И теперь вы можете использовать те же действия для уничтожения объекта employee.
Допустим, у вас есть destroy функция указателя в Person что указывает на
уничтожить функции:

void destroy_person(Person *person)
{
if(person == NULL)
return NULL;

free(person);
}

и в Person_new вы добавляете:

self->destroy = destroy_person;

Теперь вы можете уничтожить человека объекта, делая person->destroy(person);.

В destroy для Employee будет выглядеть display2:

void destroy_employee(Person *self)
{
if(self == NULL)
return;

if(self->base)
self->base->destroy(self->base);

free(self);
}

потому что self->base по-прежнему указывает на исходный объект,
self->base->destroy указывает на destroy_person. Сейчас в
Employee_new вы должны добавить

employee->super.display = display2; // override
employee->super.destroy = destroy_employee; // override

и в main вы просто делаете person->destroy(person)и
employee->destroy(employee);. Я проверил код выше (я обновил с
уничтожить функций) с Valgrind и он сказал мне, что все было освобождено
правильно.

4
ответ дан 5 марта 2018 в 12:03 Источник Поделиться

Вопрос-может мелкую копию именами

Ниже код делает копию указателя, учитывая, что ОП, например, указатель на строковый литерал - в таком случае, self->first = first; self->last = last; оба должны были const char *. Я бы ожидал, что выделение памяти для дублирования строк с strdup() или его эквивалент функции - образца. strdup() не является частью стандартной библиотеки C, но часто доступны.

// self->first = first;
// self->last = last;
self->first = strdup(first);
self->last = strdup(last);

2
ответ дан 5 марта 2018 в 12:03 Источник Поделиться