Typedef указатель абстрактного типа на конкретный указатель типа, чтобы избежать приведения в программировании на c - PullRequest
0 голосов
/ 17 октября 2019

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

foo.h

#pragma once
typedef struct foo Foo; // abstract: never defined in the program and used as pointer
Foo* foo_create(void);
void foo_transmogrify(Foo* f);

fooImplBar.c

#include "foo.h"
#include "bar.h" // from a library that defines Bar 

Foo* foo_create(void)
{
    Bar* bar = bar_create();
    return (Foo*) bar; // the cast is needed here to avoid the error
}

void foo_transmogrify(Foo* f)
{
     bar_transmogrify((Bar*) f); // another cast here
}

fooImplBaz.c

#include "foo.h"
#include "baz.h" // from a library that defines Baz

Foo* foo_create(void)
{
    Baz* baz = baz_create();
    return (Foo*) baz; // the cast is needed here to avoid the error
}

void foo_transmogrify(Foo* f)
{
     baz_transmogrify((Baz*) f); // another cast here
}

Есть ли способ избежать этих приведений?

Другими словами, есть ли способ ввести typedef Bar*и Baz* до Foo* в соответствующих папках? Я не могу изменить определения Bar и Baz на определение Foo в папках реализации, поскольку они (Bar и Baz) поступают из библиотеки. Таким образом, здесь шаблон для пересылки объявить в заголовке и определить в файле .c не применим. Один из способов избежать приведения - использовать void* вместо Foo* в целом, за счет потери информации о типе, что, на мой взгляд, хуже, чем приведения.

1 Ответ

0 голосов
/ 17 октября 2019

Вариант 1

Вы можете попробовать использовать union . Это сделает интерфейс "foo" зависимым от всех реализаций (здесь: "bar" и "baz"). Преимущество заключается в том, что нет необходимости менять реализацию.

foo.h

#include "bar.h"
#include "baz.h"

typedef union {
    Bar *bar;
    Baz *baz;
} Foo;

Foo foo_create(void);
void foo_transmogrify(Foo f);

fooImplBar.c

#include "foo.h"

Foo foo_create(void)
{
    Foo foo;
    foo.bar = bar_create();
    return foo;
}

void foo_transmogrify(Foo f)
{
    bar_transmogrify(f.bar);
}

fooImplBaz.c

#include "foo.h"

Foo foo_create(void)
{
    Foo foo;
    foo.baz = baz_create();
    return foo;
}

void foo_transmogrify(Foo f)
{
    baz_transmogrify(f.baz);
}

Вы сохраняете всю информацию о типах.

Я пробовал это с простым проектом, но Bar и Baz не полностью определены в соответствующих заголовках. Он также должен работать с полными типами.

bar.h

typedef struct bar Bar;
Bar* bar_create(void);
void bar_transmogrify(Bar* f);

bar.c

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

#include "bar.h"

typedef struct bar {
    int i;
} Bar;

Bar* bar_create(void)
{
    Bar* bar = malloc(sizeof (Bar));
    bar->i = 23;
    return bar;
}

void bar_transmogrify(Bar* f)
{
    printf("Bar: %d\n", f->i);
}

baz.h

typedef struct baz Baz;
Baz* baz_create(void);
void baz_transmogrify(Baz* f);

baz.c

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

#include "baz.h"

typedef struct baz {
    float f;
} Baz;

Baz* baz_create(void)
{
    Baz* baz = malloc(sizeof (Baz));
    baz->f = 3.14159;
    return baz;
}

void baz_transmogrify(Baz* f)
{
    printf("Baz: %f\n", f->f);
}

main.c

#include "foo.h"

int main(void)
{
    Foo f;
    f = foo_create();
    foo_transmogrify(f);
    return 0;
}

Да, я знаю, есть утечка памяти, если этот код используется в каком-то серьезном сценарии. Некоторые ba[rz]_destroy() будут необходимы. Но это небольшая проблема, которую легко решить.

Компиляция выглядит следующим образом:

gcc -Wall -Wextra -pedantic -c fooImplBar.c -o fooImplBar.o
gcc -Wall -Wextra -pedantic -c fooImplBaz.c -o fooImplBaz.o
gcc -Wall -Wextra -pedantic -c bar.c -o bar.o
gcc -Wall -Wextra -pedantic -c baz.c -o baz.o
gcc -Wall -Wextra -pedantic -c main.c -o main.o
gcc -Wall -Wextra -pedantic main.o fooImplBar.o bar.o -o mainImplBar
gcc -Wall -Wextra -pedantic main.o fooImplBaz.o baz.o -o mainImplBaz

Вариант 2:

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

"foo.h" идентично вашему.

fooImplBar.c

#include "foo.h"
#include "bar.h"

Foo* foo_create(void)
{
    Foo* bar = bar_create();
    return bar;
}

void foo_transmogrify(Foo* f)
{
    bar_transmogrify(f);
}

fooImplBaz.c

#include "foo.h"
#include "baz.h"

Foo* foo_create(void)
{
    Foo* baz = baz_create();
    return baz;
}

void foo_transmogrify(Foo* f)
{
     baz_transmogrify(f);
}

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

bar.h

typedef struct foo {
    int i;
} Foo;

Foo* bar_create(void);
void bar_transmogrify(Foo* f);

bar.c

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

#include "bar.h"

Foo* bar_create(void)
{
    Foo* bar = malloc(sizeof (Foo));
    bar->i = 23;
    return bar;
}

void bar_transmogrify(Foo* f)
{
    printf("Bar: %d\n", f->i);
}

baz.h

typedef struct foo {
    float f;
} Foo;

Foo* baz_create(void);
void baz_transmogrify(Foo* f);

baz.c

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

#include "baz.h"

Foo* baz_create(void)
{
    Foo* baz = malloc(sizeof (Foo));
    baz->f = 3.14159;
    return baz;
}

void baz_transmogrify(Foo* f)
{
    printf("Baz: %f\n", f->f);
}

main.c

#include "foo.h"

int main(void)
{
    Foo* f;
    f = foo_create();
    foo_transmogrify(f);
    return 0;
}

Компиляция выполняется как в варианте 1.


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

Это несколько проще, но может быть несовместимо с вашими настройками.

Вот так будет выглядеть пример. «foo.h» и «main.c» идентичны.

bar / foo.c

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

#include "../foo.h"

typedef struct foo {
    int i;
} Foo;

Foo* foo_create(void)
{
    Foo* foo = malloc(sizeof (Foo));
    foo->i = 23;
    return foo;
}

void foo_transmogrify(Foo* f)
{
    printf("Bar: %d\n", f->i);
}

baz / foo.c

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

#include "../foo.h"

typedef struct foo {
    float f;
} Foo;

Foo* foo_create(void)
{
    Foo* foo = malloc(sizeof (Foo));
    foo->f = 3.14159;
    return foo;
}

void foo_transmogrify(Foo* f)
{
    printf("Baz: %f\n", f->f);
}

Вы надеваетебольше не нужны оболочки "fooImplBar" и "fooImplBaz".

Компиляция:

gcc -Wall -Wextra -pedantic -c bar/foo.c -o bar/foo.o
gcc -Wall -Wextra -pedantic -c baz/foo.c -o baz/foo.o
gcc -Wall -Wextra -pedantic -c main.c -o main.o
gcc -Wall -Wextra -pedantic main.o bar/foo.o -o mainBar
gcc -Wall -Wextra -pedantic main.o baz/foo.o -o mainBaz
...