Рассказ о типичной ошибке, связанной с typedef и const. Никогда на это не попадалась, к счастью...
typedef - это не простая подстановка. Поэтому некоторые моменты, связанные с typedef'ом, могут удивить.
Допустим, в коде объявлены какие-то такие переменные.
const char* pX, Y, Z; //1
Через некоторое время мы решаем использовать typedef.
typedef char* pChar; //2
И после этого переписываем первый кусок кода как
const pChar pX, Y, Z; //3
Что неправильно, потому что первый и третий код не эквивалентны. Третий код без typedef'а будет выглядеть так.
char * const pX, * const Y, * const Z;
Тут не только Y и Z совсем не того типа, которого хотелось, но и у pX const применен к самому указателю, а не к тому, на что тот указывает. Всё это потому, что здесь
const pChar pX, Y, Z;
мы объявили pX, Y и Z константами типа pChar, а это не эквивалентно замене char* другим словом.
Поэтому в winnt.h, есть typedef'ы как для неконстантных вариантов указателей, так и для константных.
typedef char* LPSTR;
typedef const char* LPCSTR;
(реально там объявления более лохматые, но смысл такой)В статье Const input parameters and typedefs нам советуют избегать typedef'ов для типов с указателями и использовать const только явно и когда он нужен.
Пример кода я взяла отсюда:
comp.lang.c - typedef const char*
17 коммент.:
Кстати, об этом очень подробно писалось в книге Expert C Programming (автор Peter van der Linden). Книга полна мелких неожиданностей, разборов заблуждений и неплохого юмора.
У вас несколько существенных ошибок. Во-первых, в первой строке const относится ко всем переменным, а неинициализированные константы - это ошибка. Во-вторых, не забываем, что указатель в первой строке относится только к первой переменной. Таким образом, замена char* на pChar даже без const неправомерна - тогда все три переменные станут указателями. Ограничьтесь одной переменной для примера :)
typedef по-моему работает как алиас, т.е. вводит новое имя для типа, но не определяет его =)
Сергей Кищенко:
У вас несколько существенных ошибок. Во-первых, в первой строке const относится ко всем переменным, а неинициализированные константы - это ошибка.
Не хочу загромождать код инициализацией, смысл происходящего в общем ясен.
Во-вторых, не забываем, что указатель в первой строке относится только к первой переменной. Таким образом, замена char* на pChar даже без const неправомерна - тогда все три переменные станут указателями.
Угу, собственно речь и об этом тоже.
Вообще-то да. Если мы определим
typedef int BlaBlaBla;
то впоследствии сможем использовать BlaBlaBla там, где должен быть int и наоборот.
Как и сказал Юрий, это всего лишь дополнительный алиас.
2Yuriy Volkov:
typedef по-моему работает как алиас, т.е. вводит новое имя для типа, но не определяет его =)
И это правильно... изменила формулировку.
Да ну, детская совершенно ошибка. Решается "приклеиванием" звёздочки к имени идентификатора, а не к типу.
К тому же, обычно ошибаются в обратную сторону:
char* a, b, c;
...еще можно ошибочно воспринять как декларацию трёх указателей. Но
LPCHAR a, b, c;
...воспринять как декларацию одного указателя и двух статических - надо очень сильно постараться.
Или я опять чего-то не понял? =)
А, ясно. Статья по ссылке немного более внятная, т.к. нет ударения на самый очевидный косяк - "*" перед первым идентификатором и её отсутствие перед остальными.
2zorgg:
Да ну, детская совершенно ошибка. Решается "приклеиванием" звёздочки к имени идентификатора, а не к типу.
Угу, можно еще в coding conventions это записать для верности...
Или я опять чего-то не понял? =)
Ну основное тут всё-таки, что const оказался не там где ожидалось.
Алёна:
Не хочу загромождать код инициализацией, смысл происходящего в общем ясен.
Просто вы описываете одну ошибку, а там их много. Кто-то прочтет, описанную ошибку не допустит, но зато допустит множество других.
Ну основное тут всё-таки, что const оказался не там где ожидалось.
Да, уже допёр. Кстати, странно, что я на эти грабли ни разу не наступил (при том, что никогда об этом не задумывался).
2Сергей Кищенко:
Просто вы описываете одну ошибку, а там их много. Кто-то прочтет, описанную ошибку не допустит, но зато допустит множество других.
Ээээ... Ну если подойти к вопросу формально, то я говорю, что один код не эквивалентен другому. Про то, что они оба не компиляются речь не идет :-)
Сергей, я действительно считаю, что инициализация константы в данном случае - это фигня. Компилятор сразу даст внятную диагностику и проблем не возникнет...
Всегда предпочитаю вместо const T* писать T const*
typedef - это определение нового типа, а не простая подстановка.
новый тип typedef-ом не создается. проверить можно через typeid() и перегруженные шаблоны:
template <typename T> class x {};
template <> class x<bool> {};
typedef bool tbool;
template <> class x<tbool> {} // error
2Raider:
новый тип typedef-ом не создается. проверить
Да, да, меня уже попинали. Не везде исправила, пропустила...
На такие грабли, как по ссылке, не наступал - т.к. никогда не мог запомнить, к чему относится const при записи указателей (с typedef понятно, без - я впадаю в ступор).
Вообще, конечно, система описания типов в С++ - не супер... в том же C# более разумный подход.
В статье Const input parameters and typedefs нам советуют избегать typedef'ов для типов с указателями и использовать const только явно и когда он нужен.
Это называется дуть на воду, обжегшись на молоке.
Да, к сожалению, синтаксис в этом месте гибок до такой степени, что тупая макроподстановка или замена меняет смысл, не создавая синтаксических ошибок.
Но это не повод отказываться от const внутри typedef'ов.
Вообще я заметил, что проблемы с константностью вылезают тогда, когда есть проблемы с архитектурой. Если ты наперёд не знаешь, можно или нет модифицировать данный объект в данном месте, то тупо работаешь с ним через неконстантные ссылки/указатели/методы. И от этого места расползается волна неконстантности на всю программу.
В результате уже невозможно сказать - будет ли меняться состояние системы, или нет, в каком-то отдалённом месте.
Такой дурацкой практике здорово способствует COM, поскольку в IDL константность накладывается только на типы-значения (строки, структуры). А все объекты - вынь да положь неконстантность.
Отправить комментарий