Является ли typedef'ing тип указателя считаться плохой практикой?

Возможный дубликат:
Указатели Typedef - хорошая идея?

Я видел эту странность во многих API, которые я использовал:

typedef type_t *TYPE;

Моя точка зрения заключается в том, что объявление переменной типа TYPE не даст понять, что на самом деле объявлен указатель.

Вы, как и я, думаете, что это вызывает много путаницы? Это значит обеспечить инкапсуляцию, или есть другие причины? Считаете ли вы, что это плохая практика?

Ответ 1

В общем, это плохая практика. Существенная проблема заключается в том, что она не хорошо работает с const:

typedef type_t *TYPE;
extern void set_type(TYPE t);

void foo(const TYPE mytype) {
  set_type(mytype);  // Error expected, but in fact compiles
}

Чтобы автор foo() мог выразить то, что они на самом деле означают, библиотека, предоставляющая TYPE, должна также предоставить CONST_TYPE:

typedef const type_t *CONST_TYPE;

так что foo() может иметь подпись void foo(CONST_TYPE mytype), и в этот момент мы спустились в фарс.

Следовательно, эмпирическое правило:

Сделать typedefs структур (особенно неполных структур), а не указывать на эти структуры.

Если определение базовой структуры не должно быть общедоступным (что часто похвально), то эта инкапсуляция должна предоставляться структурой, являющейся неполной, а не неудобной typedefs:

struct type_t;
typedef struct type_t type_t;

void set_type(type_t *);
int get_type_field(const type_t *);

Ответ 2

Общей идиомой является суффикс типа с _p, чтобы указать, что это указатель, сохраняя при этом свойства pointery.

Иногда необходимо использовать только тип указателя, если структура, на которую он указывает, не является общедоступной. Это помогает упростить скрытие данных. То есть.

typedef struct hidden_secret_object * object;
void change_object(object foo);

это позволяет изменить способ структурирования hidden_secret_object без нарушения внешнего кода.

Ответ 3

Я тоже не понимаю. Я тоже не люблю полностью капитализированные типы (я пытаюсь зарезервировать их для #defines).

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

Суффикс с (как упоминалось ранее) _p, _ptr, Pointer или что-либо в этом направлении создает ясность; увеличивает типизацию, это правда, но предотвратит вас от глупых ошибок (например, используя "." вместо "- > ",...), которые ценят вам ценное время разработки.

Ответ 4

Это зависит от того, чего вы пытаетесь достичь. Нет никакого значимого ответа "да" или "нет" на ваш вопрос, как это указано.

  • Если вы пытаетесь создать тип абстрактного дескриптора типа, подразумевая, что пользователь не должен знать или заботиться о том, что скрывается за типом, тогда typedef-ing тип указателя отлично подходит. Все дело в том, что сегодня это может быть тип указателя, а завтра он может стать целым типом, а позже он может стать чем-то другим. Это именно то, что тип typedef типа указателя обычно используется для большинства интерфейсов библиотеки.

Вы говорите, что иногда "не ясно, что указатель объявлен". Но под этой моделью использования, что именно точка! Предполагается, что это "непонятно". Тот факт, что тип оказывается запутанным указателем, не является вашим бизнесом. Это то, что вам не нужно знать и не полагаться на него.

Классическим примером этой модели использования является тип va_list в стандартной библиотеке. В некоторой реализации это может легко быть typedef для типа указателя. Но это то, о чем вы не должны знать или полагаться.

Другим примером может быть определение типа HWND в Windows API. Это также тип typedef для типа указателя, но это не ваш бизнес.

  • Совершенно другая ситуация заключается в том, что вы печатаете тип указателя как форму сокращения, просто чтобы сделать объявления короче, чтобы не набирать символ * каждый раз. В этом случае пользователю предоставляется пользовательский тип typedef (и всегда будет), стоящий за типом указателя. Обычно это использование не является хорошей практикой программирования. Если пользователи захотят создать псевдоним, чтобы не набирать * каждый раз, они могут сделать это сами.

Эта модель использования обычно приводит к большему обфускации кода по причинам, которые вы уже упомянули в своем OP.

Пример этого плохого использования typedefs также можно найти в Windows API. Названия Typedef, такие как PINT, соответствуют именно этой ошибочной модели использования.

Ответ 5

Я не думаю, что это плохая практика, если это указатель на неполный тип, или если по какой-либо другой причине пользователь не должен разыменовывать его. Я никогда не понимал FILE*.

Я также не думаю, что это плохая практика, если вы это делаете, потому что у вас есть несколько уровней косвенности, и вы хотите использовать ее в ситуациях, когда некоторые из них не имеют значения. typedef char **argarray или что-то в этом роде.

Если пользователь должен разыменовать его, то в C, я думаю, что лучше всего сохранить *. В С++ люди используются для пользовательских типов с перегруженным operator*, например, итераторами. В C это просто не нормально.

Ответ 6

Возможно, более конкретным способом было бы вызвать новый тип указателя type_ptr или что-то вроде этого:

 typedef type_t* type_ptr;

Ответ 7

Квалификаторы класса хранения, такие как "const", будут работать по-разному с указателями typedef, чем с "естественными". Хотя это обычно не очень хорошо для "const", это может быть очень полезно для классов хранения специфичных для компилятора, таких как "xdata". Объявление типа:

xdata WOKKA *foo;

объявит "foo" указателем, хранящимся в классе хранения по умолчанию, в WOKKA в xdata. Декларация:

xdata WOKKA_PTR bar;
объявит "bar" указателем, хранящимся в xdata, в WOKKA в любом классе хранения, указанном в WOKKA_PTR. Если библиотечные процедуры ожидают указания указателей на вещи с определенным классом хранения, может оказаться полезным определить эти классы хранения в типах указателей.

Ответ 8

Он иногда укусил меня в попку:

for (vector<typedef_name_that_doesnt_indicate_pointerness_at_all>::iterator it;
    it != v.end(); ++it)
{
   it->foo(); // should have been written (*it)->foo();
}

Единственное время, когда это приемлемо, - это тип, который должен быть действительно непрозрачным и вообще недоступным. IOW, если кто-то захочет разыграть его за пределами API, то pointerness не следует скрывать за typedef.