LINUX.ORG.RU

Как задать «полупрозрачную» структуру не повторяясь?

 , ,


1

4

Здравствуйте

Положим, есть интерфейс, описанный в foo.h:

typedef struct foo_obj_tag foo_obj;

bool foo_do_something(foo_obj *foo);
...

И есть две отдельные реализации этого интерфейса, в файлах bar.c и baz.c:

/* bar.c */
#include "foo.h"
struct foo_obj_tag {
  int clear;
  char *clear2;
  int opaq_bar;
}
/* baz.c */
#include "foo.h"
struct foo_obj_tag {
  int clear;
  double opaq_baz;
}

В структуре есть прозрачные поля (clear) и непрозрачные, которые отличаются в разных реализациях. Всё прекрасно работает, за исключением того неприятного момента, что приходится повторять описание прозрачных полей структуры (их довольно много) в каждом внутреннем файле библиотеки (в тестах, например):

struct foo_obj_tag {
  int clear;
}

Как бы и тортик съесть и вишенкой не подавиться?) И определить структуру с прозрачными полями в заголовочном файле, и специфически «расширить» эту структуру в файлах-реализациях?

★★★★★

Последнее исправление: makoven (всего исправлений: 1)

Сделай свою структуру с одними лишь прозначными полями, и вставляй ее внутрь всех твоих других структур, где эти прозрачные поля нужны(структура может содержать внутри себя другую структуру)

SZT ★★★★★
()
Ответ на: комментарий от SZT

Точно! А это идея. Только, наверное, наоборот - прозрачную структуру с одним полем типа void*, в которую вставлять что угодно спецефическое. Иначе не получится красивых вызовов foo_something(foo_obj foo, etc)

Так и сделаю пока, если ничего интереснее предложат)

makoven ★★★★★
() автор топика
Последнее исправление: makoven (всего исправлений: 1)
Ответ на: комментарий от makoven

В любом случае вставки кода с #define #ifdef и пр. работать будут, хотя код получится слегка объемным.

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

aiqu6Ait ★★★★
()
Последнее исправление: aiqu6Ait (всего исправлений: 1)
Ответ на: комментарий от SZT

А если наружу должна торчать только struct linked_list, а linked_data - это скрытые детали которые меняются от реализации к реализации и нигде кроме файла реализации видны быть не должны? Скажем, в int_list.c структура linked_data будет содержать только поле int a, а в float_list.c - только поле float b. Причем для конечного пользователя это значения не имеет - он не лезет в linked_data

makoven ★★★★★
() автор топика
Ответ на: комментарий от makoven

Не вижу проблемы. Если конечному пользователю не нужна linked_data, так пусть и не трогает ее. Пусть работает исключительно с linked list. Можно еще через макросы наплодить кучу структур почти как linked list только чтоб внутри было еще что-то. Типа такого

#define MKSTRUCT(structname, otherstruct) struct structname\
{\
  void *next;\
  void *prev;\
  struct otherstruct data;\
}

MKSTRUCT(somestruct, someotherstruct);

SZT ★★★★★
()
Ответ на: комментарий от SZT

Если конечному пользователю не нужна linked_data, так пусть и не трогает ее

Так в заголовочном файле (API для конечного пользователя) linked_data не опишешь. Т.к эта структура в разных реализациях разная. Конечный пользователь получает лишь объект linked_list, который хранит всё свое состояние

Можно, конечно, макросами расширить, но больно некрасиво получается imho

makoven ★★★★★
() автор топика

-fplan9-extensions + gcc

#define foo_obj_tag_base struct {int clear;}

typedef foo_obj_tag_base foo_obj_tag_t;

typedef struct {
  foo_obj_tag_base;
  int t;
} foo_obj_tag2_t;

typedef struct {
  foo_obj_tag_t;//-fplan9-extensions
  int t;
} foo_obj_tag3_t;

AnonCxx
()
Ответ на: комментарий от makoven

Так в заголовочном файле (API для конечного пользователя) linked_data не опишешь. Т.к эта структура в разных реализациях разная.

Ну так задокументируй для пользователей эту структуру частично. Раз конечному пользователю не нужно работать с чем либо кроме inked_data, эту другую часть можно просто не описывать. По-моему ты выдумываешь проблемы на ровном месте

SZT ★★★★★
()

#define FOO int first; char *second

struct foo_obj_tag { FOO; double bar, baz; }

arturpub ★★
()
Ответ на: комментарий от makoven

Обычно в С наследование делают приблизительно вот так:

struct linked_data {
  struct linked_data *prev;
  struct linked_data *next;
  int type_of_data;
}

void* new_linked_elem(int size, int dtype) {
   struct linked_data *e = malloc(size);
   e->prev = NULL;
   e->next = NULL;
   e->type_of_data = dtype;
   return e;
}

void* next_elem(void *e) {
  return (struct linked_data*)(e)->next;
}
void* prev_elem(void *e) {
  return (struct linked_data*)(e)->prev;
}

struct my_list_1 {
  struct linked_data ltd;
  double value;
}

void some_func_1() {
  struct my_list_1 *l1 = new_linked_elem(sizeof(my_list_1), 1);
  l1 = next_elem(l1);
  l1 = prev_elem(l1); 
}

struct my_list_2 {
  struct linked_data ltd;
  int value;
}
void some_func_2() {
  struct my_list_2 *l2 = new_linked_elem(sizeof(my_list_2), 2);
  l2 = next_elem(l2);
  l2 = prev_elem(l2); 
}
Для примеров можете посмотреть glib там полный набор ООП на C (хотя мне жутко не нравится - но это дело вкуса)

zaz ★★★★
()
Ответ на: комментарий от makoven

Что тебе непонятно?

#define foo_obj_tag_base struct {int clear;}//это анонимная структура

typedef foo_obj_tag_base foo_obj_tag_t;//это на неё тайпдеф

typedef struct {
  foo_obj_tag_base;//это её определение в структуре.
  struct {int clear;}//так можно
  int t;
} foo_obj_tag2_t;

typedef struct {
  foo_obj_tag_t;//-fplan9-extensions// это позволяет чистое наледование.
  foo_obj_tag2_t;//добавляет в структуру все поля из foo_obj_tag2_t. 
  int t;
} foo_obj_tag3_t;

AnonCxx
()
Ответ на: комментарий от zaz

С первого раза не понял, отдохну, перечитаю)

makoven ★★★★★
() автор топика
Ответ на: комментарий от makoven

Тебе хватит и этого:


#define foo_obj_tag_base struct {int clear;}//это анонимная структура

typedef foo_obj_tag_base foo_obj_tag_t;//это на неё тайпдеф

typedef struct {
  foo_obj_tag_base;//это её определение в структуре.
  struct {int clear;}//так можно
  int t;
} foo_obj_tag2_t;

Это стандарт.

AnonCxx
()
Ответ на: комментарий от alx777

Хорошая но под GPL, а у простых пацанов есть <sys/queue.h>

anonymous
()
Ответ на: комментарий от alx777

Реализация довольно отвратная, потому что struct list_head мало что говорит тебе о типе элементов списка. Т.е. ты можешь случайно засунуть что-нибудь не того типа и узнать об этом после долгого и нудного дебага. Лучше уж <sys/queue.h>.

kirk_johnson ★☆
()
/* shared_stuff.inc */
/* big fat warning on why it's not a proper header file */
int clear;
/* bar.c */
#include "foo.h"
struct foo_obj_tag {
  #include "shared_stuff.inc"
  char *clear2;
  int opaq_bar;
}
/* baz.c */
#include "foo.h"
struct foo_obj_tag {
  #include "shared_stuff.inc"
  double opaq_baz;
}
sf ★★★
()
Ответ на: комментарий от AnonCxx

Может я тупой, но не могу понять как мне это поможет)

Если предпологается что структура_1 уже определена в .h-файле, то я не смогу ее доопределить в другом файле. Я смогу только создать структуру_2 и засунуть в нее анонимную структуру_1. Но тогда рушится красивенький интерфейс foo_f(struct_1 obj). Придется таскать обе foo_f(struct_1, struct_2). Или на некоторых функциях 1, на некоторых 2.. Бардак, в общем)

makoven ★★★★★
() автор топика
Ответ на: комментарий от kirk_johnson

Я привел list.h в качестве примера как такие вещи делаются на C, сделать #include «list.h» в юзерспейсе врядли удастся

Реализация довольно отвратная, потому что struct list_head мало что говорит тебе о типе элементов списка

Обычно заранее известно что в списке, по крайней мере у меня с этим проблем за все время использования не было

А что, есть реализация на C где можно узнать тип хранимых элементов ?

Лучше уж <sys/queue.h>

Для юзерспейса лучше, согласен

Кстати, в Убунте оказывается можно выбирать из двух альтернатив

$ find /usr/include -name "queue.h" |grep sys
/usr/include/bsd/sys/queue.h
/usr/include/x86_64-linux-gnu/sys/queue.h
alx777 ★★
()
Ответ на: комментарий от makoven

Если предпологается что структура_1 уже определена в .h-файле, то я не смогу ее доопределить в другом файле.

А с чего ты должен это мочь - это тебе жабаскрипт что-ли?

#define struct_base struct {int clear;}

typedef struct {
  struct_base;
//   ...
} iface_t;//у тебя определяется это в каждом интерфейсе и при экспорте интерфейса экспортируется нужная структура.


#define struct_iface1 struct {int a;}
#define struct_iface2 struct {float b;}

#define struct_iface//выбираешь нужную индефами, либо экспортируешь её вместе с интерфейсом

typedef struct {
  int clear;
  struct_iface;
} struct_t;

Можно просто задефайнить(либо заинклюдить, как предложили выше) поля, а не анонимную структуру.

Если тебе нужно передавать её куда-то, где нужна базовая структура - можно как-то так накастылять:

#define struct_base struct {int clear;}

typedef struct_base iface_base_t;

#define struct_base_def iface_base_t base[0]; struct_base

typedef struct {
  struct_base_def;
  float b;
} iface_t;


void base_iface(iface_base_t * o) {
  fprintf(stderr, "%d\n", o->clear);
}


void iface(iface_t * o) {
  fprintf(stderr, "%f ", o->b); base_iface(o->base);
}

int main(void) {
  iface(&(iface_t){.clear = 10, .b = 20.3});
}
AnonCxx
()
Ответ на: комментарий от alx777

Я привел list.h в качестве примера как такие вещи делаются на C, сделать #include «list.h» в юзерспейсе врядли удастся

Загляни в util-linux, они их из ядра под копирку передрали.

Обычно заранее известно что в списке, по крайней мере у меня с этим проблем за все время использования не было

Кому известно? Компилятор ничего об этом не знает — list_add()/list_del() оперируют указателями на struct list_head.

А что, есть реализация на C где можно узнать тип хранимых элементов ?

Угу. В <sys/queue.h>.

Кстати, в Убунте оказывается можно выбирать из двух альтернатив

Это не альтернативы.

kirk_johnson ★☆
()
Ответ на: комментарий от zaz

Получается, мы засовываем структуру s_1 в начало структуры s_2, а затем, при необходимости, кастуем s_2 в s_1, принимая во внимание тот файкт, что компилятор плотненько разместил поля s_1 в начале s_2.

А насколько это переносимо и стандартно? Не может так случиться, что какой-то другой компилятор не просто разместит поля s_1 в начале s_2, но еще и добавит какой-нибудь заголовок перед полями s_1?

makoven ★★★★★
() автор топика
Ответ на: комментарий от makoven

А насколько это переносимо и стандартно? Не может так случиться, что какой-то другой компилятор не просто разместит поля s_1 в начале s_2, но еще и добавит какой-нибудь заголовок перед полями s_1?

Ты можешь сделать __packed для пущей убедительности.

kirk_johnson ★☆
()
Ответ на: комментарий от makoven

Для C это «переносимо и стандартно» для С++ нет (так как там есть всякие таблици виртуальных методов, мутанты и тд - но там есть наследование).

Выравнивание всегда осуществляетя методоб добавления лишних байт В КОНЕЦ предыдущего поля но никак не в начало текущего. Тоесть если компилятор решит выравнять структуру s_1 то он это будет делать изменяя ее размер (добавит пару байт в конец) а не ее расположение в памяти + как сказали выше вы всегда можете управлять выравниванием через «pragma pack».

Ну и повторюсь еще раз - посмотрите дно из наиболее распрастраненных реализаций ООП на С - glib там наследование реализовано именно таким способом.

zaz ★★★★
()
Ответ на: комментарий от zaz

Понял. Спасибо. Может и правда пришло время окунуться с головой в glib, начать писать в парадигме gobj, освоить gtk.. )

makoven ★★★★★
() автор топика
Ответ на: комментарий от kirk_johnson

Кому известно? Компилятор ничего об этом не знает — list_add()/list_del() оперируют указателями на struct list_head.

Не нашел чем queue.h в этом плане лучше. Совершенно так же в список можно засунуть неизвестно что

#include <stdio.h>
#include <sys/queue.h>
#include <stdlib.h>

struct Num {
  int val;
  LIST_ENTRY(Num) num;
};

struct NumExt {
  int val0;
  int val1;
  int val2;
  LIST_ENTRY(NumExt) num;
};

int main(void)
{
  int i;
  struct Num *pn;
  struct NumExt pn_ext;
  
  printf("Queue test application\n");
  LIST_HEAD(NumsHead, Num) nums_head;
  LIST_INIT(&nums_head);
  
  pn_ext.val0 = 999;
  LIST_INSERT_HEAD(&nums_head, &pn_ext, num);
  
  for (i = 0; i < 2; i++) {
    pn = malloc(sizeof(struct Num));
    pn->val = i *10;
    LIST_INSERT_HEAD(&nums_head, pn, num);
  }
  
  i = 0;
  LIST_FOREACH(pn, &nums_head, num) {
    printf("item %d val %d\n", i, pn->val);
    i ++;
  }
  return 0;
}
$ gcc queue-test.c -o queue-test -Wall
$ ./queue-test 
Queue test application
item 0 val 10
item 1 val 0
item 2 val 999
item 3 val -1991643855
Segmentation fault (core dumped)

Угу. В <sys/queue.h>

Можно пример ?

Это не альтернативы.

Отчего же ? Что мне мешает использовать оригинальный BSD вариант ?

alx777 ★★
()
Ответ на: комментарий от alx777

Можно пример ?

Код:

#include <stdlib.h>
#include <sys/queue.h>

struct foo {
	int a;
	TAILQ_ENTRY(foo) entry;
};
TAILQ_HEAD(foo_queue, foo);

struct bar {
	void *ptr;
	TAILQ_ENTRY(bar) entry;
};
TAILQ_HEAD(bar_queue, bar);

int main(void)
{
	struct bar       b;
	struct foo_queue fq;

	TAILQ_INIT(&fq);
	TAILQ_INSERT_TAIL(&fq, &b, entry);
	exit(EXIT_SUCCESS);
}

Вывод:

$ clang -Wall test.c   
test.c:23:2: warning: incompatible pointer types assigning to 'struct bar **' from 'struct foo **' [-Wincompatible-pointer-types]
        TAILQ_INSERT_TAIL(&fq, &b, entry);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/sys/queue.h:404:24: note: expanded from macro 'TAILQ_INSERT_TAIL'
        (elm)->field.tqe_prev = (head)->tqh_last;                       \
                              ^ ~~~~~~~~~~~~~~~~
test.c:23:2: warning: incompatible pointer types assigning to 'struct foo *' from 'struct bar *' [-Wincompatible-pointer-types]
        TAILQ_INSERT_TAIL(&fq, &b, entry);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/sys/queue.h:405:20: note: expanded from macro 'TAILQ_INSERT_TAIL'
        *(head)->tqh_last = (elm);                                      \
                          ^ ~~~~~
test.c:23:2: warning: incompatible pointer types assigning to 'struct foo **' from 'struct bar **' [-Wincompatible-pointer-types]
        TAILQ_INSERT_TAIL(&fq, &b, entry);
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/sys/queue.h:406:19: note: expanded from macro 'TAILQ_INSERT_TAIL'
        (head)->tqh_last = &(elm)->field.tqe_next; 

Отчего же ? Что мне мешает использовать оригинальный BSD вариант ?

Ничего. А вот внутренности gcc не являются публичным API. И завтра его оттуда могут выкинуть.

P.S. Лулз в том, что у gcc < 5.x похоже просто забивает на несовпадение типов.

kirk_johnson ★☆
()
Последнее исправление: kirk_johnson (всего исправлений: 2)
Ответ на: комментарий от kirk_johnson

Вывод:

С clang у меня то же самое. Надо будет глянуть в каком состоянии там поддержка ARMa

Ничего. А вот внутренности gcc не являются публичным API. И завтра его оттуда могут выкинуть.

Не все так плохо, оно не в gcc

$ dpkg -S /usr/include/x86_64-linux-gnu/sys/queue.h
libc6-dev:amd64: /usr/include/x86_64-linux-gnu/sys/queue.h

alx777 ★★
()

Подклассы есть в Glib, помнится.

Можно просто включать структуру родительского класса в начало структуры подкласса. Тогда не прийдётся «повторять описание прозрачных полей структуры».

Более безопасный и более общепринятый шаблон программирования в С - непрозрачный (opaque) указатель на специфические данные в «видимой» структуре. Смотри ffmpeg, asterisk и ещё кучу других вменяемых проектов.

Andrey_Utkin ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.