Как воспроизвести поведение методов класса в C99? - PullRequest
0 голосов
/ 05 февраля 2019

У меня есть простой код для расчета периметра многоугольника

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

struct point { float x; float y; };

struct polygon
{
    int sides;
    struct point vertex[20];                // No more than 20 sided polygon
    float (*perimeter)(struct polygon * p);
};

struct polygon * polygon_init(int sides)
{
    struct polygon *poly;
    poly = malloc(sizeof *poly);
    poly->sides = sides;
    return poly;
}

void polygon_destroy(struct polygon * poly)
{
    free(poly);
}

float distance(struct point p1, struct point p2)
{
    return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}

// Assuming points are ordered ;-)
float perimeter(struct polygon * poly)
{
    float p = 0;
    for (int i = 0; i < poly->sides - 1; i++)
        p += distance(poly->vertex[i], poly->vertex[i+1]);
    return p + distance(poly->vertex[poly->sides - 1], poly->vertex[0]);
}

int main(void)
{
    struct polygon *p1 = polygon_init(3);
    struct point t[3] = { {0, 0}, {.5, 1}, {1, 0}};
    memcpy(p1->vertex, t, sizeof(t));
    p1->perimeter = &perimeter;
    printf("perimeter = %.2f\n", p1->perimeter(p1));
    polygon_destroy(p1);
    return 0;
}

Как вы можете заметить, я использую указатель на функцию, чтобы назначить "пользовательскую" функцию для вычисления периметра (p1->perimeter = &perimeter;).При этом я могу «абстрагировать» вызов на основе структуры, присвоенной p1.

. Но, как это заметно, я передаю ссылку на саму структуру в качестве параметра функции perimeter() как p1->perimeter(p1), иэто выглядит в значительной степени избыточно.

Я хотел бы знать, есть ли способ вызвать p1->perimeter(), вместо вызова выше и внутри perimeter() функция будет выполняться автоматически (знайте, что я ссылаюсь на p1), сказал, что вычислит сам периметр p1.

В одной фразе: я пытаюсь подделать поведение методов класса.

Ответы [ 3 ]

0 голосов
/ 05 февраля 2019

Инициализируйте его в polygon_init:

poly->perimeter = perimeter;

Если ваши интерфейсы объектов становятся большими, вы можете захотеть реализовать правильную виртуальную таблицу (VT), которая является просто структурой указателей на функции дляучебный класс.Каждый объект затем хранит указатель на VT, а не на все функции в отдельности, и для любого данного класса необходим только один const VT.Таким образом, размер объекта не изменяется в зависимости от количества виртуальных методов.

0 голосов
/ 05 февраля 2019

Я хотел бы знать, если есть способ вызвать p1->perimeter(), вместо вызова выше и внутри perimeter() функция будет автоматически (знаю, что я ссылаюсь на p1), сказал, что этовычислил бы периметр самого p1.

C не обеспечивает какого-либо удовлетворительного способа передать объект для perimeter() для работы, кроме как через аргумент функции.Существует множество механизмов, с помощью которых вы могли бы хранить указатель на struct polygon где-то, что функция знает, чтобы искать его, но все они грязные и / или не поточнобезопасные, и все они требуют какой-то операцииили дополнительный код в дополнение к выбору функции и вызову функции.

Оператор -> просто не передает никакой информации о своем (левом) операнде в свой результат, и даже если он это сделал, выражения вызова функции Cпередавать информацию в вызываемую функцию только через аргументы функции.

@ Ответ Джона Кугельмана уже представляет идиоматический подход на языке Си к этой проблеме: передать объект для обработки в качестве аргумента.Обычно это делает вместо , выбирая функцию через член объекта-операнда, и таким образом избегает избыточности, на которую вы указали в вопросе.

Идя в противоположном направлении, затемЕсть несколько забавных игр, в которые вы можете играть в C11 и позже, используя generic selection , чтобы выбрать функцию для вызова в зависимости от типа аргумента.Обычно это оборачивается в общий макрос.Возможно, это уменьшит для вас важность записи функций в качестве членов объектов, над которыми они должны работать.Например:

#define perimeter(x) _Generic((x),          \
    struct circle *:   circle_perimeter,    \
    struct polygon *:  poly_perimeter,      \
    struct path *:     path_perimeter)(x)

Затем, когда вы вызываете perimeter(a_shape_pointer), вы вызываете функцию, соответствующую типу аргумента.

0 голосов
/ 05 февраля 2019

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

float polygon_perimeter(struct polygon *p)
{
    return p->perimeter(p);
}

Это хорошо, потому что он параллелен polygon_init() и polygon_destroy(): все функции находятся за пределами «класса», а не эта конкретная функция вызывается через указатель члена только потому, что она полиморфна.

Она также предотвращает утечку внутренних элементов struct polygon пользователям.Есть много преимуществ, если вам требуется, чтобы весь доступ к «классу» осуществлялся через непрозрачный указатель и библиотеку вызовов функций.Прямой доступ к элементам отсутствует, поэтому макет struct может свободно изменяться без риска поломки API.

Если вы пойдете по этому пути, то сможете полностью скрыть его внутренние компоненты, предоставив только пустое объявление вперед в polygon.h:

// polygon.h
struct polygon;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...