Аннотация над реализацией типа в C - PullRequest
0 голосов
/ 26 ноября 2018

Я новичок в программировании на C и пытаюсь написать простой пример.Точно я попытался абстрагироваться от реализации типа и просто использовать typedef и указать операции, которые я могу сделать с этим типом.Я понимаю, что в этот момент тип является неполным, но я собирался завершить его в c -файл, а не в заголовок.Вот оно:

test.h

#ifndef _TEST_H
#define _TEST_H

typedef my_type_t;

void init(my_type_t **t);

#endif //_TEST_H

test.c

#include <stdlib.h>
#include "test.h"
                  //      implementation details
struct my_type_t{ //<---- completening my_type_t to be a struct with 1 field
    int field;
};

void init(struct my_type_t **t){ //<--- error: conflicting type for init
    *t = malloc(sizeof(struct my_type_t));
    (*t) -> field = 42;
}

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

UPD: если мы переписываем c -файл следующим образом:

#include <stdlib.h>
#include "test.h"

struct internal_my_type_definition_t{
    int field;
};

void init(my_type_t **t){
    struct internal_my_type_definition_t *st = malloc(sizeof(struct internal_my_type_definition_t));
    st -> field = 42;
    *t = st;
}

Есть ли проблемы с такой реализацией?

Ответы [ 2 ]

0 голосов
/ 26 ноября 2018

Шаблон дизайна, который вы ищете, называется «непрозрачный тип» / «непрозрачные указатели».

У вас почти все правильно, вам просто нужно явно указать тип в заголовке:

typedef struct my_type_t my_type_t;

Это и определение типа, и предварительное объявление неполного типа, которое заполняется в вашем файле .c и не отображается для вызывающей стороны.

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

0 голосов
/ 26 ноября 2018

В вашем заголовке измените

typedef my_type_t;

на

struct my_type_t;

Это довольно распространенный шаблон.Просто имейте в виду, что вам понадобится функция для выделения структуры в куче и ее освобождения;одна из частей информации, которую вы скрываете, - это размер структуры, поэтому потребитель API может иметь дело только с указателями на структуру, а не с самой структурой.

Идиоматический API будет выглядеть примерно так:

struct my_type_t* my_type_new(void);
void my_type_free(struct my_type_t* self);

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

Редактировать : в ответ на ваш дополнительный вопрос вы можете сделать что-то подобное в заголовке:

#if !defined(MY_TYPE_NS)
#  define MY_TYPE_NS struct
#endif

typedef MY_TYPE_NS my_type_t my_type;

my_type* my_type_new(void);

/* ... */

Затем в файле * .c:

#define MY_TYPE_NS union
#include "test.h"

union my_type_t {
  /* ... */
};

my_type* my_type_new(void*) {
  my_type* res = malloc(sizeof(my_type));
  res->field = 42;
  return res;
}

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

...