Обеспечьте сильную проверку типов в C (строгость строгости для typedefs)
Есть ли способ принудительного применения явного приведения для typedef одного типа? Я должен иметь дело с utf8, и иногда меня путают с индексами для количества символов и количества байтов. Поэтому было бы неплохо иметь некоторые typedefs:
typedef unsigned int char_idx_t;
typedef unsigned int byte_idx_t;
С добавлением, что вам нужно явное литье между ними:
char_idx_t a = 0;
byte_idx_t b;
b = a; // compile warning
b = (byte_idx_t) a; // ok
Я знаю, что такая функция не существует в C, но, возможно, вы знаете трюк или расширение компилятора (предпочтительный gcc), который делает это.
ИЗМЕНИТЬ
Мне все еще не очень нравится венгерская нотация в целом. Я не мог использовать его для этой проблемы из-за соглашений о кодировании проекта, но теперь я использовал его в другом подобном случае, где также типы одинаковы, а значения очень похожи. И я должен признать: это помогает. Я никогда не стал бы объявлять каждое целое число со стартовым "i", но, как в примере Joel для перекрывающихся типов, это может быть спасение жизни.
Ответы
Ответ 1
Вы можете сделать что-то вроде:
typedef struct {
unsigned int c_idx;
} char_idx;
typedef struct {
unsigned int b_idx;
} byte_idx;
Затем вы увидите, когда будете использовать каждый из них:
char_idx a;
byte_idx b;
b.b_idx = a.c_idx;
Теперь более понятно, что они разные, но все еще компилируются.
Ответ 2
Для типов "handle" (непрозрачные указатели) Microsoft использует трюк объявления структур, а затем typedef'ing указатель на структуру:
#define DECLARE_HANDLE(name) struct name##__ { int unused; }; \
typedef struct name##__ *name
Тогда вместо
typedef void* FOOHANDLE;
typedef void* BARHANDLE;
Они делают:
DECLARE_HANDLE(FOOHANDLE);
DECLARE_HANDLE(BARHANDLE);
Итак, теперь это работает:
FOOHANDLE make_foo();
BARHANDLE make_bar();
void do_bar(BARHANDLE);
FOOHANDLE foo = make_foo(); /* ok */
BARHANDLE bar = foo; /* won't work! */
do_bar(foo); /* won't work! */
Ответ 3
То, что вы хотите, называется "сильный typedef" или "strict typedef".
Некоторые языки программирования [Rust, D, Haskell, Ada,...] дают некоторую поддержку этому на уровне языка, C [++] этого не делает. Было предложено включить его в язык с названием "непрозрачный typedef", но не был принят.
Отсутствие поддержки языка действительно не проблема. Просто оберните тип, который будет сглажен, в новый класс, имеющий ровно 1 элемент данных типа T. Большая часть повторения может быть учтена шаблонами и макросами. Этот простой метод так же удобен, как и на языках программирования с прямой поддержкой.
Ответ 4
Используйте lint. См. Splint: Types и проверка сильного типа.
Сильная проверка типов часто показывает ошибки программирования. Шина может проверять примитивные типы С более строго и гибко, чем типичные компиляторы (4.1) и поддерживает булевский тип (4.2). Кроме того, пользователи могут определить абстрактные типы, которые обеспечивают скрытие информации (0).
Ответ 5
В C единственное различие между определяемыми пользователем типами , выполняемое компилятором, - это различие между структурами. Любой typedef, включающий различные структуры, будет работать. Ваш главный вопрос дизайна - , если разные типы структур используют одни и те же имена участников? Если это так, вы можете имитировать некоторый полиморфный код с использованием макросов и других трюков. Если нет, вы действительно привержены двум различным представлениям. Например, хотите ли вы иметь возможность
#define INCREMENT(s, k) ((s).n += (k))
и используйте INCREMENT
как для byte_idx
, так и для char_idx
? Затем назовите поля одинаково.
Ответ 6
Вы спрашивали о расширениях. Jeff Foster CQual очень приятный, и я думаю, что он мог бы выполнить нужную работу.
Ответ 7
Если вы писали С++, вы могли бы создать два одинаково определенных класса с разными именами, которые были обертками вокруг unsigned int. Я не знаю трюк, чтобы делать то, что вы хотите в C.
Ответ 8
Используйте сильное typedef, как определено в BOOST_STRONG_TYPEDEF
Ответ 9
С С++ 11 вы можете использовать класс enum, например
enum class char_idx_t : unsigned int {};
enum class byte_idx_t : unsigned int {};
Компилятор будет принудительно применять явное литье между двумя типами; это похоже на тонкий класс обертки. К сожалению, у вас не будет перегрузки оператора, например. если вы хотите добавить два char_idx_t вместе, вам нужно будет отнести их к unsigned int.