Указатели typedef - это хорошая идея?

Я просмотрел некоторый код и заметил, что соглашение заключалось в том, чтобы переключать такие типы указателей, как

SomeStruct* 

в

typedef SomeStruct* pSomeStruct;

Есть ли в этом заслуга?


person Unknown    schedule 15.04.2009    source источник
comment
Это распространено в C. Потому что вы пытаетесь скрыть тот факт, что это указатель. Это объект, который вы передаете всем интерфейсам в библиотеке. В C ++ это нечасто и не приветствуется, хотя и не является чем-то неслыханным.   -  person Martin York    schedule 15.04.2009
comment
Интересная статья на эту общую тему: Беседы: ночное безумие в летнее время См. Также стиль кодирования ядра Linux для крайняя точка зрения на «не определяйте типы указателей (и не предоставляйте определения типов для структур или объединений)».   -  person Jonathan Leffler    schedule 15.01.2019
comment
@MartinYork Можно добавить, что в C ++ он не нужен, так как поддерживает передачу по ссылке.   -  person klutt    schedule 11.05.2021
comment
@klutt В C ++ вы все равно используете методы, а не функции, поэтому скрытый объект (this) всегда присутствует.   -  person Martin York    schedule 11.05.2021


Ответы (14)


Это может быть уместно, когда сам указатель можно рассматривать как «черный ящик», то есть фрагмент данных, внутреннее представление которого не должно иметь отношения к коду.

По сути, если ваш код никогда не разыменовывает указатель, и вы просто передаете его функциям API (иногда по ссылке), тогда typedef не только уменьшает количество *s в вашем коде, но также предлагает программисту, что указатель действительно не должен вмешиваться.

Это также упрощает изменение API в будущем, если возникнет такая необходимость. Например, если вы перейдете на использование идентификатора, а не указателя (или наоборот), существующий код не сломается, потому что указатель никогда не должен был быть разыменован в первую очередь.

person Artelius    schedule 15.04.2009
comment
Пример из реальной жизни: в OS X платформа CoreFoundation широко использует эту технику, называя их непрозрачными типами данных. - person hbw; 15.04.2009
comment
Также в OS X: pthread_t - непрозрачный тип; он определен как указатель на struct _opaque_pthread_t, которая сама является непрозрачным массивом байтов. - person Adam Rosenfield; 15.04.2009
comment
Пример счетчика: FILE * fp? Собственно, я с вами согласен, но прецедентов обратного есть. - person Jonathan Leffler; 15.04.2009
comment
Конечно, если вы сделаете это, вы не назовете непрозрачный тип pFoo. - person MSalters; 15.04.2009
comment
И ключевым моментом является то, что когда вы объявляете переменную этого непрозрачного типа, то (за пределами библиотеки, которая реализует поддержку этого типа) вы никогда не применяете переменную- ›member; вы всегда обращаетесь только к переменной и передаете ее функциям и т. д. Вы также не увеличиваете или не уменьшаете переменную. - person Jonathan Leffler; 16.04.2009
comment
Ого, я никогда не считал FILE * совершенно ненужным. Было бы неплохо, если бы они так давно напечатали. - person Matt Joiner; 09.06.2010
comment
Также обратите внимание, что typedef-указатель может значительно повысить удобочитаемость. Представьте, что у вас есть std::map<int, std::map<std::string, std::vector<int> > > в качестве переменной. Если вам нужно определить множество таких переменных в разных местах вашего кода, использование красивого typedef может сделать работу, а также чтение кода более приятным занятием. - person rbaleksandar; 30.05.2016
comment
@MattJoiner: причина, по которой FILE* не был определен по типу как непрозрачный указатель, заключается в том, чтобы позволить макросам, таким как getc() и putc(), напрямую манипулировать структурой FILE и избежать накладных расходов на вызов функции в большинстве случаев. - person chqrlie; 18.08.2016
comment
@rbaleksandar В этом примере используется псевдоним типа, а не указателя. И указатель на такую ​​карту лучше не иметь псевдонима. - person Aconcagua; 11.12.2018
comment
В дополнение к @JonathanLeffler: лучше всего, если - в общедоступном заголовке - тип непрозрачного указателя был только объявлен, но определен в другом месте: struct XYZ; typedef struct XYZ* Handle; - если реализация требуется несколько единиц компиляции, он может быть определен в отдельном закрытом заголовке ... - person Aconcagua; 11.12.2018
comment
Полностью согласен с данным вариантом использования, был бы признателен, если бы ответ был более четким о том, что это должен быть исключительный случай (в общем, если указатель предназначен для разыменования, лучше не быть псевдонимом). - person Aconcagua; 11.12.2018
comment
Некоторые люди могут захотеть увидеть пример указателя черного ящика, в который нельзя напрямую вмешиваться. Итак, вот полный пример, который я написал для непрозрачного указателя (или: указатель на непрозрачную структуру) в C, который отлично демонстрирует это, включая typedef непрозрачного указателя. Непрозрачные структуры C: как их объявлять? - person Gabriel Staples; 23.10.2020

Не по моему опыту. Скрытие "*" затрудняет чтение кода.

person sigjuice    schedule 15.04.2009
comment
Не знаю об этом. Я имею в виду, что если у вас есть указатель, скажем, Device, а вы typedef, это Device_Ptr (вместо того, чтобы использовать Device *) или что-то подобное, он вполне читаем. Я вижу много хорошо продуманных и довольно больших библиотек, которые это делают. Если вы добавляете шаблоны, особенно в C ++, это избавляет ваши пальцы от необходимости печатать. - person rbaleksandar; 15.02.2017
comment
@rbaleksandar снова нормально, если Device * непрозрачный дескриптор. - person Alex; 06.04.2018
comment
Как C n00b, я думаю, что синтаксис указателя C - это мерзость - он нравится только тем несчастным душам, которые уже потратили более 10 лет на то, чтобы научиться любить его. Я бы взял myTypePtr или myTypeRef над этой звездой в любой день. :П - person Christoffer Bubach; 30.06.2020

Единственный раз, когда я использую указатель внутри typedef, - это когда имею дело с указателями на функции:

typedef void (*SigCatcher(int, void (*)(int)))(int);

typedef void (*SigCatcher)(int);

SigCatcher old = signal(SIGINT, SIG_IGN);

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


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

 typedef SigCatcher (*SignalFunction)(int, SigCatcher);

Или, чтобы объявить функцию signal():

 extern SigCatcher signal(int, SigCatcher);

То есть SignalFunction - это указатель на функцию, которая принимает два аргумента (int и SigCatcher) и возвращает SigCatcher. А сама signal() является функцией, которая принимает два аргумента (int и SigCatcher) и возвращает SigCatcher.

person Jonathan Leffler    schedule 15.04.2009
comment
Работает ли этот typedef с меньшим количеством знаков препинания? typedef void SigCatcher (интервал, void (*) (интервал)) (интервал) - person sigjuice; 18.04.2009
comment
Нет; gcc сообщает об ошибке: SigCatcher объявлен как функция, возвращающая функцию. - person Jonathan Leffler; 18.04.2009
comment
Я просто натолкнулся на еще более простое определение типа signal () freebsd.org /cgi/man.cgi?query=signal - person sigjuice; 06.11.2010
comment
@Sigjuice: даже не переходя на страницу BSD (чего я не делал до тех пор, пока не отредактировал материал), я увидел огромные проблемы с моим ответом, которые теперь исправлены. И то, что BSD называет sig_t, совпадает с моим SigCatcher, и да, это значительно упрощает объявление signal(). - person Jonathan Leffler; 07.11.2010
comment
Круто. Эквивалентное объявление с явным * выглядит не так уж плохо, ИМХО. typedef void SigCatcher (int); extern SigCatcher * signal (int, SigCatcher *); - person sigjuice; 07.11.2010

Это может помочь вам избежать некоторых ошибок. Например, в следующем коде:

int* pointer1, pointer2;

Указатель2 не является int *, это просто int. Но с typedef этого не произойдет:

typedef int* pInt;
pInt pointer1, pointer2;

Теперь они оба int *.

person Sad Developer    schedule 15.04.2009
comment
Если вы поместите * касание указателя1, а не касание int, в объявлении будет гораздо больше смысла, что указатель2 не является указателем, а просто int. - person dreamlax; 15.04.2009
comment
Я думаю, программисты на C возразят, что использование оператора разыменования при объявлении переменной-указателя - это скорее идиоматическая вещь. Например, в K&R они говорят: int * ip задумано как мнемоника; он говорит, что выражение '* ip' является int. - person hbw; 15.04.2009
comment
2 dreamlax: это все еще менее читаемый и последовательный код, объявляющий две переменные разных типов в одной строке. - person Sad Developer; 15.04.2009
comment
Но каждый знает этот синтаксис, и в любом случае лучше объявлять и инициализировать одну переменную за раз ... Я редко, если вообще когда-либо, пишу int * pointer1, * pointer2; потому что в следующей строке у вас есть два хороших указателя с неопределенным значением. - person Daniel Daranas; 15.04.2009
comment
Я думаю, что '' 'int * pointer, * pointer2' '' все еще уязвим для ошибок. - person Sad Developer; 16.04.2009
comment
Роальт хотел сказать: «int * pointer1» вместо «int * pointer1». - person Artelius; 08.11.2009
comment
@ Дэниел Даранас: int *p1 = NULL, *p2 = NULL; - person Matt Joiner; 09.06.2010
comment
@Matt Joiner Вы правы. Хотя я и так обычно использую две строчки. - person Daniel Daranas; 09.06.2010
comment
Типичный пример защиты плохой практики еще более плохой практикой. Никогда не объявляйте более одной переменной в одной строке, потому что для этого нет причин, точка. Это приведет только к ошибкам. И вся эта дискуссия, касающаяся указателя, - ерунда, потому что, если вы в первую очередь пишете хороший код, расположение * не имеет значения. - person Lundin; 03.12.2015

Мой ответ - однозначное «Нет».

Почему?

Во-первых, вы просто меняете один символ * на другой отдельный символ p. Это нулевой прирост. Одно это должно удерживать вас от этого, потому что делать лишние бессмысленные вещи всегда плохо.

Во-вторых, и это важная причина, * несет значение, которое не следует скрывать. Если я передам что-то такой функции

void foo(SomeType bar);

void baz() {
    SomeType myBar = getSomeType();
    foo(myBar);
}

Я не ожидаю, что значение myBar изменится, если передать его foo(). В конце концов, я передаю по значению, так что foo() когда-либо видит только копию myBar, верно? Не тогда, когда псевдоним SomeType означает какой-то указатель!

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

(Я считаю, что привычка определять типы указателей - это просто ошибочная попытка скрыть, сколько звездочек у человека, как у программиста http://wiki.c2.com/?ThreeStarProgrammer.)

person cmaster - reinstate monica    schedule 09.04.2017
comment
+1. Другая причина в том, что не будет никакой защиты системы от void *vp=/*...*/; foo(vp); или foo(0);. (Определения типов для перечислений и определения числовых типов для вещей, которые никогда не должны отображаться как числа, имеют аналогичные проблемы). - person PSkocik; 11.11.2018

Это вопрос стиля. Вы очень часто видите такой код в заголовочных файлах Windows. Хотя они, как правило, предпочитают версию в верхнем регистре вместо строчной буквы p.

Лично я избегаю этого использования typedef. Намного понятнее, если пользователь явно скажет, что ему нужен Foo *, чем PFoo. Typedef в наши дни лучше всего подходят для чтения в STL :)

typedef stl::map<stl::wstring,CAdapt<CComPtr<IFoo>> NameToFooMap;
person JaredPar    schedule 15.04.2009

Это (как и многие ответы) зависит.

В C это очень распространено, поскольку вы пытаетесь замаскировать объект как указатель. Вы пытаетесь намекнуть, что это объект, которым манипулируют все ваши функции (мы знаем, что это указатель внизу, но он представляет объект, которым вы управляете).

MYDB   db = MYDBcreateDB("Plop://djdjdjjdjd");

MYDBDoSomthingWithDB(db,5,6,7);
CallLocalFuc(db); // if db is not a pointer things could be complicated.
MYDBdestroyDB(db);

Под MYDB, вероятно, находится указатель на какой-то объект.

В C ++ это больше не требуется.
В основном потому, что мы можем передавать информацию по ссылке, а методы включены в объявление класса.

MyDB   db("Plop://djdjdjjdjd");

db.DoSomthingWithDB(5,6,7);
CallLocalFuc(db);   // This time we can call be reference.
db.destroyDB();     // Or let the destructor handle it.
person Martin York    schedule 15.04.2009

No.

Это сделает вашу жизнь несчастной, как только вы смешаете ее с const

typedef foo *fooptr;
const fooptr bar1;
const foo *bar2

bar1 и bar2 одного типа?

И да, я просто цитирую Гуру Херба Саттера. Она говорила много правды. ;)

-- Редактировать --

Добавление ссылки на цитируемую статью.

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

person dgnuff    schedule 16.01.2019
comment
Не могли бы вы добавить ссылку на упомянутую вами статью? К тому же он мужчина! :-) - person Fabio says Reinstate Monica; 19.02.2019
comment
@ Азат Ибраков Это мелочь, но хотя Херб определенно парень, Гуру, о котором он пишет, - женщина. Следовательно, ее уместно называть женским началом. Да, я знаю, что она фактически является альтер-эго для Херба, но если вы прочитаете всю серию, включая историю, рассказанную в первых 20 или около того статьях, для нее имеет смысл быть тем, кем она является. - person dgnuff; 31.12.2019

Typedef используется, чтобы сделать код более читаемым, но использование указателя в качестве typedef вызовет путаницу. Лучше избегать указателей typedef.

person Chand    schedule 15.04.2009

Обсуждение проводится при условии, что интересующий язык - C. Разветвления для C ++ не рассматривались.

Использование typedef указателя для немаркированной структуры

Вопрос Размер структуры, которая определяется как указатель, поднимает интересный побочный свет при использовании typedef для указателей (структуры).

Рассмотрим определение бетонного (непрозрачного) типа структуры без тегов:

typedef struct { int field1; double field2; } *Information;

Детали участников совершенно не касаются этого обсуждения; все, что имеет значение, это то, что это не непрозрачный тип, такой как typedef struct tag *tag; (и вы не можете определить такие непрозрачные типы с помощью typedef без тега).

Возникает вопрос: «Как узнать размер этой структуры»?

Краткий ответ: «только через переменную типа». Нет тега для использования с sizeof(struct tag). Вы не можете использовать, например, sizeof(*Information), а sizeof(Information *) - это размер указателя на тип указателя, а не размер типа структуры.

Фактически, если вы хотите выделить такую ​​структуру, вы не можете создать ее, кроме как с помощью динамического распределения (или суррогатных методов, имитирующих динамическое распределение). Невозможно создать локальную переменную типа структуры, указатели которой называются Information, также нет способа создать переменную области видимости файла (глобальную или static) типа структуры, а также нет способа встроить такую ​​структуру (в отличие от указателя на такую ​​структуру) в другую структуру или тип объединения.

Вы можете - должны - написать:

Information info = malloc(sizeof(*info));

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

Это вредно?

Это зависит от ваших целей.

Это не непрозрачный тип - детали структуры должны быть определены, когда тип указателя typedef'd.

Это тип, который можно использовать только с динамическим распределением памяти.

Это безымянный тип. Указатель на тип структуры имеет имя, а сам тип структуры - нет.

Если вы хотите принудительно использовать динамическое распределение, это, кажется, способ сделать это.

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

Резюме

В общем, использование typedef для определения указателя на тип структуры без тегов - плохая идея.

person Jonathan Leffler    schedule 09.04.2017

Если вы сделаете это, вы не сможете создавать STL-контейнеры const pSomeStruct, поскольку компилятор читает:

list<const pSomeStruct> structs;

as

list<SomeStruct * const> structs;

который не является легальным контейнером STL, поскольку элементы не могут быть присвоены.

См. Этот вопрос.

person Dan Hook    schedule 27.01.2010
comment
Конечно, но никто бы никогда не стал писать list<const ordinary_type>, так как это все равно не сработает, так что нет никакой путаницы. - person ; 21.04.2014
comment
Я не совсем понимаю, что вы имеете в виду под ordinary_type. list<const SomeStruct*> вполне допустим, поскольку const SomeStruct* является CopyAssignable. - person Dan Hook; 22.04.2014

Win32 API делает это практически с каждой структурой (если не со всеми).

POINT => *LPPOINT
WNDCLASSEX => *LPWNDCLASSEX
RECT => *LPRECT
PRINT_INFO_2 => *LPPRINT_INFO_2

Приятно, что это единообразно, но, на мой взгляд, элегантности это не добавляет.

person dreamlax    schedule 15.04.2009
comment
Это потому, что Win32 - это C API, и он распространен в C, а не в C ++. - person Martin York; 15.04.2009
comment
Вопрос помечен как C, так и C ++, в чем конкретно ваша точка зрения? - person dreamlax; 15.04.2009
comment
Следует отметить, что это не обычная практика и в C, Win API - практически единственная известная библиотека C, которая делает это. Он также использует венгерскую нотацию. С точки зрения стиля это очень непонятная библиотека, и вы не должны рассматривать ее как каноническую. - person Lundin; 14.04.2016
comment
Это обозначение восходит к истокам Windows 30 лет назад в 16-битной сегментированной архитектуре Intel. LP означает длинный указатель, а сокращенная запись LPPOINT ptr; была не чем иным, как удобным сокращением для POINT FAR *ptr;, FAR, расширяющимся до far, а затем до __far. Все это довольно неэлегантно, но раньше было еще хуже. - person chqrlie; 18.08.2016

Цель typedef - скрыть детали реализации, но определение типа свойства указателя скрывает слишком многое и затрудняет чтение / понимание кода. Так что, пожалуйста, не делай этого.


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

FILE *fopen(const char *filename, const char *mode);
char *fgets(char *s, int size, FILE *stream);

здесь fopen возвращает указатель на некоторую структуру FILE (детали реализации которой вы не знаете). Возможно, FILE не такой уж хороший пример, потому что в этом случае он мог работать с каким-то типом pFILE, который скрывал тот факт, что это указатель.

pFILE fopen(const char *filename, const char *mode);
char *fgets(char *s, int size, pFILE stream);

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

person hlovdal    schedule 15.04.2009

Некоторое время назад я бы ответил «нет» на этот вопрос. Теперь, с появлением интеллектуальных указателей, указатели больше не всегда обозначаются звездочкой '*'. Таким образом, нет ничего очевидного в том, является ли тип указателем или нет.

Итак, теперь я бы сказал: указатели typedef - это нормально, если четко указано, что это «тип указателя». Это означает, что вы должны использовать префикс / суффикс специально для этого. Нет, например, «p» не является достаточным префиксом. Я бы, наверное, выбрал "ptr".

person Benoît    schedule 15.04.2009
comment
Если вы не собираетесь использовать его в качестве указателя, в этом случае вы можете называть его как хотите. Если он просто используется для передачи информации (например, C-ФАЙЛ *), имеет ли значение, что именно? - person David Thornley; 16.04.2009
comment
Хорошая точка зрения. Я пытался добавить несколько плюсов к списку минусов, которые были высказаны против указателей определения типа ... - person Benoît; 16.04.2009