Пытаюсь систематизировать операторы приведения типов. На примере С/С++ и подобных языков "среднего" уровня.
В С++ есть 5 способов приведения типов — один сишный и 4 оператора cast.
Понятно что сишный остался ради совместимости, он неудобен тем что очень сложно его искать в коде (нет ключевого слова), т.е. с точки зрения дизайна языка такой оператор нежелателен.
А какое количество операторов приведения типов вы считаете разумным, если бы мы могли перепроектировать С++ заново?
Очевидно, что должен быть оператор динамического приведения (dynamic_cast), который проверяет возможность приведения, используя дополнительную информацию.
Но: такой оператор может возвразать null (нулевой указатель или в общем случае значение по умолчанию типа, к которому приводим), а может выкидывать исключение. То есть уже как-бы два оператора.
Статическое преобразование типа.
Должен быть некий "тривиальный" оператор, который явно приводит то, что компилятор неявно приводить не хочет. Аналог static_cast и частично приведения в стиле Си.
Фактически — приведение для устранения ворнингов (не ошибок). Расширение типа (из int в double), потеря точности (из double в int), во всех случаях когда есть возможность явного преобразования но нет возможности неявного и т.п.
reinterpret_cast.
Вероятно, должен быть некий сверхнизкоуровневый оператор для приведения через область памяти (чего угодно к чему угодно). Мало ли.
С константностью:
Как я понимаю, идеологически правильнее иметь два типа константности: истинную (объект создается один раз и далее его невозможно изменить никогда) и локальную (явно запрещаем изменять объект в данном контексте). Если обратиться к языку D, то первое — это immutable, второе — const. Хотя я бы сказал что второе это "readonly".
В связи с этим для преобразования из immutable в изменяемый объект — только reinterpret_cast (реально низкоуровневый оператор для хакерских целей). Можно, например, обратиться к памяти и попытаться изменить константный литерал.
Для снятия readonly — должен быть какой-то отдельный оператор типа const_cast. Это более серьезно чем статическое приведение, это не динамическое приведение, и это менее серьезно чем reinterpret_cast.
Получается, что кажущаяся довольно громоздкой система приведения типов в С++ тем ни менее достаточно продумана. Или нет? Какие есть нюансы, которые не учтены в С++, которы я здесь не учел?
Здравствуйте, NeoCode, Вы писали:
NC>Получается, что кажущаяся довольно громоздкой система приведения типов в С++ тем ни менее достаточно продумана. Или нет? Какие есть нюансы, которые не учтены в С++, который я здесь не учел?
Я бы оставил только статическое приведение на этапе компиляции (в которое бы попали static, const и reinterpret), и динамическое на этапе выполнения кода. А так да, с одной стороны эти преобразования типов можно отделить. Но какая от этого практическая польза? Почему должно быть что-то отдельное? Приведение типов выполняется на так уж и часто, более того по двум типам более-менее понятно, как их можно привести один в другой. Очень похоже на случай "лишний член — жопе непонятка".
Не говоря про то, что можно
// (1) заменить конструкторами/методами для числовых типов
int32_t value1 = -3;
printf("%u", uint32_t(value1));
// (2) ввести метод address для преобразования адресовtypedef const void * ptr_t;
uintptr_t & address(ptr_t &);
void * ptr = malloc(32);
address(struct_ptr) = address(ptr);
Здравствуйте, Mystic, Вы писали:
M>Я бы оставил только статическое приведение на этапе компиляции (в которое бы попали static, const и reinterpret), и динамическое на этапе выполнения кода. А так да, с одной стороны эти преобразования типов можно отделить. Но какая от этого практическая польза? Почему должно быть что-то отдельное? Приведение типов выполняется на так уж и часто, более того по двум типам более-менее понятно, как их можно привести один в другой. Очень похоже на случай "лишний член — жопе непонятка".
Когда есть методы — это хорошо, лично для меня методы нагляднее всего. Но они есть не всегда.
Почему разные? Ну ИМХО потому что они действительно разные. Например, из float в int можно привести "статически", а можно через "reinterpet" (это будет эквивалентно *(int*)&float_var). Т.е. reinterpret — это некая низкоруровневая вещь, которую я бы рассматривал скорее рядом с ассемблерными вставками, а не как приведение типов.
const_cast — это для явного обхода запрета на модификацию. Хотя как раз насчет const_cast у меня большие сомнения в целесообразности.
Практическая польза — в том, что их можно найти в коде поиском. Иногда при больших изменениях кода (особенно чужого) это бывает полезно.
Хотя эти ужасные длинные названия мне не нравятся, надо придумать что-то покороче.
Про dynamic: в некоторых случаях невозможность приведения — нормальная ситуация и null допустим. В некоторых — нет, и нужно исключение. Хотя это может быть более общий вопрос дизайна языков программирования...
NC>Например, из float в int можно привести "статически", а можно через "reinterpet" (это будет эквивалентно *(int*)&float_var).
Через reintepret у меня не получилось:
строка
int32_t y = reinterpret_cast<int32_t>(x);
дает
error: invalid cast from type ‘float’ to type ‘int32_t {aka int}’
Вообще, более понятно это делать через union. А так я много лет программирую на C++, но деталей всех этих извратов с приведениями стараюсь избегать: зачастую есть более или простой способ достичь требуемого или некоторый альтернативный путь, который не требует изучения стандарта и экспериментов.
NC>const_cast — это для явного обхода запрета на модификацию. Хотя как раз насчет const_cast у меня большие сомнения в целесообразности.
Пару раз сталкивался из-за кривости дизайна.
NC>Практическая польза — в том, что их можно найти в коде поиском. Иногда при больших изменениях кода (особенно чужого) это бывает полезно.
Не сталкивался с таким. Зачастую есть более насущные проблемы. Ну получу я списки static_cast и reinterpret_cast отдельно. И что это даст? Не говоря про то, что часто static или reinterpret выбирается по настроению. Надо один указатель привести к другому. Если один из них void *, то вполне подойдет static_cast. Но и reinterpret_cast вполне себе работает. Как для меня явно не то место, где следует заморачиваться. Тем более, что если возникает нужна что-то приводить на постоянной основе, то пишется wrapper.
Здравствуйте, Mystic, Вы писали:
M>Через reintepret у меня не получилось:
Да, ваш пример не работает, зато работает например такой (со ссылкой). Почему такое ограничение — не знаю, все равно reinterpret же, какая уж разница раз лезем на битовый уровень
float x = 3.14;
unsigned int &y = reinterpret_cast<unsigned int&>(x);
В LLVM есть инструкция bitcast, которая, судя по описанию, делает то же самое (кстати, красивое название для этого оператора, лучше чем громоздкий reinterpret_cast).
M>Вообще, более понятно это делать через union. А так я много лет программирую на C++, но деталей всех этих извратов с приведениями стараюсь избегать: зачастую есть более или простой способ достичь требуемого или некоторый альтернативный путь, который не требует изучения стандарта и экспериментов.
union требует объявления явной дополнительной переменной.
Здравствуйте, NeoCode, Вы писали:
NC>Здравствуйте, Mystic, Вы писали:
M>>Через reintepret у меня не получилось:
NC>Да, ваш пример не работает, зато работает например такой (со ссылкой).
Со ссылкой понятно, и с указателями можно намутить так же.
NC>В LLVM есть инструкция bitcast, которая, судя по описанию, делает то же самое (кстати, красивое название для этого оператора, лучше чем громоздкий reinterpret_cast).
В C есть memcpy для таких вещей. Можно было бы поднять его на уровень компилятора.
M>>Вообще, более понятно это делать через union. А так я много лет программирую на C++, но деталей всех этих извратов с приведениями стараюсь избегать: зачастую есть более или простой способ достичь требуемого или некоторый альтернативный путь, который не требует изучения стандарта и экспериментов. NC>union требует объявления явной дополнительной переменной.
Ну и что? Можно подумать, что reinterpret_cast это одна из самых частоиспользуемых возможностей языка, что позволит сэкономить кучу места. А так необходимость возникает раз в год, два раза на пасху. Можно и переменную описать, чтобы мозг не засорять. Заодно битовые поля могут сделать код более читаемым:
/**
* bitScanForward
* @param 64 bit unsigned integer value
* @return index (0..63) of least significant one bit
* -1023 if passing zero
*/int bitScanForward(U64 bb)
{
union {
double d;
struct {
unsigned int mantissal : 32;
unsigned int mantissah : 20;
unsigned int exponent : 11;
unsigned int sign : 1;
};
} ud;
ud.d = (double)(bb & -bb); // isolated LS1B to doublereturn ud.exponent - 1023;
}
Здравствуйте, NeoCode, Вы писали:
NC>Пытаюсь систематизировать операторы приведения типов. На примере С/С++ и подобных языков "среднего" уровня.
NC>В С++ есть 5 способов приведения типов — один сишный и 4 оператора cast. NC>Понятно что сишный остался ради совместимости, он неудобен тем что очень сложно его искать в коде (нет ключевого слова), т.е. с точки зрения дизайна языка такой оператор нежелателен.
Возьмите &. Одна и та же кракозябра используется и для создания нового типа, и для взятия адреса. И нельзя поискать "&" и найти все объявления типов или все взятия адреса отдельно друг от друга. Что, теперь будем говорить, что & неудобен, нежелателен с точки зрения на дизайн языка (а не точки зрения дизайна, кстати — у дизайна зрения нет)? Ах, вы ищете конкретные ссылочные типы Type&? Ну, так и приведение люди ищут к какому-то конкретному типу, а поиск "(Type)" даст куда более вменяемые результаты.
Раз заговорили про дизайн, есть два диаметрально разных подхода: максимально понизить порог вхождения, а там пусть мучаются, либо наплевать на порог вхождения, зато в повседневке максимально облегчить жизнь. Ну, а между ними весь спектр языковых решений. Очевидно, Си подходил к программисту со второй меркой: пусть текст на нем весьма далек от английского языка, один раз выучишь все эти &, *, (), ||, &&, зато потом при чтении не будешь отвлекаться. Подход, по сути, классически математический. Наша школьная учительница математики стебала позднесоветские учебники за многословное доказательство теорем. У нее запись с кракозябрами кванторов занимала ровно столько строчек, сколько шагов в доказательстве, а каждая строка состояла из кучки символов. Так вот, мода городить ключевые слова вместо кракозябр — это уже не Си, а смещение к противоположному полюсу. Чтобы без мануала люди могли начать писать код. И надо себе в этом отдавать отчет.
Не знаю, как сейчас, а в старину приверженность создателей первому полюсу и дала Си выиграть с ошеломляющим счетом. Только не надо снова начинать про никс и связанную с ним фору у языка. Ознакомьтесь с историей вопроса. В Microsoft, Apple и других крупных компаниях безоговорочно рулил Pascal. С ключевыми словами. Вот это была фора, так фора, что не помешало ему слиться. Читаешь мемуары, постоянно видишь — «да, он был красивый, но многословный».
NC>А какое количество операторов приведения типов вы считаете разумным, если бы мы могли перепроектировать С++ заново? NC>Очевидно, что должен быть оператор динамического приведения (dynamic_cast), который проверяет возможность приведения, используя дополнительную информацию.
Ровно один — из Си. В той же форме, без ключевых слов, по соображениям, изложенным выше. Только значить он должен что-то одно, в зависимости от языка.
dynamic_cast в C++ — это настоящая бомба. Его реализовали в C# (в виде сишного оператора, конечно — с пониманием подошли люди), но там это сделали грамотно: во-первых, динамический кастинг встроен в среду, которая неотделима от языка, во-вторых, это если и не единственный кастинг (в unsafe-части не силен), то уж точно основной в 99%. (as это не новый способ приведения, а сокращение от "obj is T ? (T) obj : null"). А что в C++? А вот что. Я лично фиксил баг одного умника, который за два месяца навтыкал повсюду dynamic_cast, а потом свалил с работы. А в мобильном проекте RTTI был выключен. В результате, программа просто грохалась на каждом. Чем думали люди, которые вводили в язык операторы, требующие соблюдения внешних условий? Короче, вот вам формальный вызов: от динамического кастинга в плюсах вреда (как в приведенном примере) больше, чем пользы. Чтобы было наоборот, нужна неотделяемая от языка среда, в которой типам уделялось бы повышенное внимание (с метаданными, и полным описанием каждого типа).
На всякий случай, отдельно: не надо перекладывать ответственность на изготовителей компиляторов, типа, стандарт прокукарекал, а они не подтянулись. Сам по себе RTTI чужероден в идеологии C++ минимального обязательного оверхеда. Его не случайно под мобильные платформы делают опциональным. А подумать об этом бардаке можно было еще в комитете.
NC>Но: такой оператор может возвразать null (нулевой указатель или в общем случае значение по умолчанию типа, к которому приводим), а может выкидывать исключение. То есть уже как-бы два оператора.
А за это я бы их откопал (после расстрела за RTTI вообще), отдельно ..., и снова закопал.
NC>reinterpret_cast. NC>Вероятно, должен быть некий сверхнизкоуровневый оператор для приведения через область памяти (чего угодно к чему угодно). Мало ли.
Для подобных махинаций с памятью (a la fast square root из Q3) должны использоваться указатели либо ничего. А облегчать написание подобных хаков отдельным оператором?
NC>С константностью: NC>Как я понимаю, идеологически правильнее иметь два типа константности: истинную (объект создается один раз и далее его невозможно изменить никогда) и локальную (явно запрещаем изменять объект в данном контексте). Если обратиться к языку D, то первое — это immutable, второе — const. Хотя я бы сказал что второе это "readonly". NC>В связи с этим для преобразования из immutable в изменяемый объект — только reinterpret_cast (реально низкоуровневый оператор для хакерских целей). Можно, например, обратиться к памяти и попытаться изменить константный литерал. NC>Для снятия readonly — должен быть какой-то отдельный оператор типа const_cast. Это более серьезно чем статическое приведение, это не динамическое приведение, и это менее серьезно чем reinterpret_cast.
Тут надо или константность/иммутабельность иметь, или приведение. Одно из двух. К private тоже особо не слазишь, так, что или указатель в зубы, или не трогай, что не должен.
NC>Получается, что кажущаяся довольно громоздкой система приведения типов в С++ тем ни менее достаточно продумана. Или нет? Какие есть нюансы, которые не учтены в С++, которы я здесь не учел?
Если это считать продуманностью, то майданную революцию придумал Черчилль в восемнадцатом году.
P.S. Я за свою жизнь ни разу не встречал необходимости использовать хоть что-то, кроме (). Вот просто ни разу. А современный C++ это какая-то адская жесть. Я сильно подозреваю, что народу не хватает C#, но 1) общественного, а не микрософтного, 2) с ручным управлением памятью или, хотя бы, полноценно настраиваемым GC, или, хотя бы, профилируемым. Вот и пытаются соорудить из C++. Получается не очень.
Здравствуйте, SV., Вы писали:
SV.>А за это я бы их откопал (после расстрела за RTTI вообще), отдельно ..., и снова закопал.
ИМХО RTTI это маленький кусочек недоделанной рефлексии. Если бы ее нормально доделали, была бы очень полезная вещь, многие извращенные велосипеды отпали бы. Не вижу почему это чуждо идеологии С++, просто почему-то вектор развития языка пошел в другую сторону.
SV.>Тут надо или константность/иммутабельность иметь, или приведение. Одно из двух. К private тоже особо не слазишь, так, что или указатель в зубы, или не трогай, что не должен.
Ну вот я примерно об этом. const при передаче ссылок/указателей в функции — это именно readonly (мы говорим, что в данном контексте переменная по этой ссылке не должна изменяться). Это именно право доступа, как private/public. При этом остается const для переменных (именованные константы), там константность связана с объектом в течение всего его времени жизни. Тут тоже две ситуации: или объект действительно иммутабельный, или проще закрыть доступ для изменения всем, кроме "избранных". Последняя ситуация ни в одном известном мне языке программирования отдельно не рассматривается. А если объект на самом деле, глобально и по дизайну — immutable, но надо поменять — только через прямой низкоуровневый доступ к памяти и вся ответственность на программисте.
SV.>P.S. Я за свою жизнь ни разу не встречал необходимости использовать хоть что-то, кроме (). Вот просто ни разу.
Лично я динамическое приведение использую. Например, есть дерево полиморфных объектов. Чтобы для определенного элемента найти в нем родителя определенного типа, иду в цикле от данного элемента по parent-указателям и проверяю dynamic_cast'ом, приводятся ли они к нужному типу. Так что динамическое приведение — вещь нужная.
Хотя не спорю, приведение в стиле C# красивее, при разработке нового языка программирования конкретно для динамического приведения лучше использовать именно оператор "as".
SV.>А современный C++ это какая-то адская жесть.
Здесь согласен. Но жесть в основном с шаблонами, которые используют не по назначению. С остальным никакой жести не вижу.
SV.>Я сильно подозреваю, что народу не хватает C#, но 1) общественного, а не микрософтного, 2) с ручным управлением памятью или, хотя бы, полноценно настраиваемым GC, или, хотя бы, профилируемым. Вот и пытаются соорудить из C++. Получается не очень.
Здравствуйте, NeoCode, Вы писали:
SV.>>А за это я бы их откопал (после расстрела за RTTI вообще), отдельно ..., и снова закопал.
NC>ИМХО RTTI это маленький кусочек недоделанной рефлексии. Если бы ее нормально доделали, была бы очень полезная вещь, многие извращенные велосипеды отпали бы. Не вижу почему это чуждо идеологии С++, просто почему-то вектор развития языка пошел в другую сторону.
Это чуждо C++ потому, что код, в котором не нужно отражение, будет иметь оверхед в виде, как минимум, одного дополнительного указателя на каждый объект. Иначе, как вы получите тип после компиляции? Кроме того, как быть с имеющимся кодом в виде бинарей + заголовочных файлов? Опять необязательно работающие операторы вводить? Спасибо, одного dynamic_cast'а хватило. Отражение в дотнете работает и приносит пользу потому, что оно там изначально и сквозно реализовано, а скромный оверхед на фоне богатых языковых возможностей там никого не е (C# не позиционируется как язык для написания производительного кода, он позиционируется как язык для RADостей).
SV.>>Тут надо или константность/иммутабельность иметь, или приведение. Одно из двух. К private тоже особо не слазишь, так, что или указатель в зубы, или не трогай, что не должен.
NC>Ну вот я примерно об этом. const при передаче ссылок/указателей в функции — это именно readonly (мы говорим, что в данном контексте переменная по этой ссылке не должна изменяться). Это именно право доступа, как private/public. При этом остается const для переменных (именованные константы), там константность связана с объектом в течение всего его времени жизни. Тут тоже две ситуации: или объект действительно иммутабельный, или проще закрыть доступ для изменения всем, кроме "избранных". Последняя ситуация ни в одном известном мне языке программирования отдельно не рассматривается. А если объект на самом деле, глобально и по дизайну — immutable, но надо поменять — только через прямой низкоуровневый доступ к памяти и вся ответственность на программисте.
Ничего не понял. Зачем какие-то приведения? Я так понял, вам хотелось *_cast, который бы снимал константность. По-моему, это способ нарушить контракт и такому не место в хороших языках. Если понял неправильно, какие другие приведения, связанные с константностью, вам нужны?
SV.>>P.S. Я за свою жизнь ни разу не встречал необходимости использовать хоть что-то, кроме (). Вот просто ни разу.
NC>Лично я динамическое приведение использую. Например, есть дерево полиморфных объектов. Чтобы для определенного элемента найти в нем родителя определенного типа, иду в цикле от данного элемента по parent-указателям и проверяю dynamic_cast'ом, приводятся ли они к нужному типу. Так что динамическое приведение — вещь нужная.
И зачем вам это нужно?
NC>Хотя не спорю, приведение в стиле C# красивее, при разработке нового языка программирования конкретно для динамического приведения лучше использовать именно оператор "as".
SV.>>А современный C++ это какая-то адская жесть. NC>Здесь согласен. Но жесть в основном с шаблонами, которые используют не по назначению. С остальным никакой жести не вижу.
Здравствуйте, SV., Вы писали:
SV.>Это чуждо C++ потому, что код, в котором не нужно отражение, будет иметь оверхед в виде, как минимум, одного дополнительного указателя на каждый объект. Иначе, как вы получите тип после компиляции? Кроме того, как быть с имеющимся кодом в виде бинарей + заголовочных файлов? Опять необязательно работающие операторы вводить? Спасибо, одного dynamic_cast'а хватило. Отражение в дотнете работает и приносит пользу потому, что оно там изначально и сквозно реализовано, а скромный оверхед на фоне богатых языковых возможностей там никого не е (C# не позиционируется как язык для написания производительного кода, он позиционируется как язык для RADостей).
Рефлексия, требующая дополнительных указателей в объектах (как и многие другие подобные вещи), должна быть опциональной и включаться/отключаться соответствующими ключевыми словами для каждого конкретного модуля, класса и функции. Понятно что в С++ этого уже не сделать, но у нас же раздел "философия", а не "с++".
SV.>Ничего не понял. Зачем какие-то приведения? Я так понял, вам хотелось *_cast, который бы снимал константность. По-моему, это способ нарушить контракт и такому не место в хороших языках. Если понял неправильно, какие другие приведения, связанные с константностью, вам нужны?
Я пытаюсь понять, нужен ли const_cast. Не в С++, а в некотором идеальном си-подобном языке программирования, охватывающем максимум возможностей (низоуровневое программирование, средний и высокий уровни). Он действительно нарушает контракт, но в С++ почему-то его сделали отдельно от reinterpret_cast, который нарушает все контракты по определению. Вот пытаюсь понять, почему.
Наверное, для константности следует создать отдельную тему, так как оказывается что там не все так просто.
Здравствуйте, NeoCode, Вы писали:
NC>Рефлексия, требующая дополнительных указателей в объектах (как и многие другие подобные вещи), должна быть опциональной и включаться/отключаться соответствующими ключевыми словами для каждого конкретного модуля, класса и функции. Понятно что в С++ этого уже не сделать, но у нас же раздел "философия", а не "с++".
А я уже сказал, как философ философу: спасибо, не надо. Это будет уже не один язык, а два, но только называться одинаково и создавать путаницу. Путаницу, типа той, что создают клавиша C/C в QWERTY/ЙЦУКЕН или необоснованное применение UTF-8. Опять какой-нибудь любитель современного сиплюсплюса нахерачит на ровном месте многочисленные typeof, уволится, а ты потом ползай, разгребай. Или скачал библиотеку, а там отражения, которые у тебя не поддерживаются. При том, что код, в принципе, пишется без них. А как быть со стандартными библиотеками? Использовать там отражения? И использовать плохо, и не использовать — глупо.
SV.>>Ничего не понял. Зачем какие-то приведения? Я так понял, вам хотелось *_cast, который бы снимал константность. По-моему, это способ нарушить контракт и такому не место в хороших языках. Если понял неправильно, какие другие приведения, связанные с константностью, вам нужны?
NC>Я пытаюсь понять, нужен ли const_cast. Не в С++, а в некотором идеальном си-подобном языке программирования, охватывающем максимум возможностей (низоуровневое программирование, средний и высокий уровни). Он действительно нарушает контракт, но в С++ почему-то его сделали отдельно от reinterpret_cast, который нарушает все контракты по определению. Вот пытаюсь понять, почему. NC>Наверное, для константности следует создать отдельную тему, так как оказывается что там не все так просто.
Мое мнение: здесь создатели нового стандарта сделали ошибку. reinterpret_cast не нужен потому, что при наличии инструментов для операций с памятью (взятие указателя, приведение типа указателя в C-стиле, разыменование), которые вместе неявно нарушают контракты, он является оператором, который делает это явно. Логики в этом я не вижу. const_cast не нужен потому, что просто не нужен. Приведите пример, когда он нужен, а я попробую переписать код, чтобы он обходился без этого оператора и не был хуже. То же относится к коду с деревом и подбором, про который вы не ответили. Я просто вспоминаю, что в плюсах я никогда не использовал RTTI и отлично себя чувствовал, а в шарпе использовал, конечно, но в таких ситуациях, где все решал динамически-бинарный характер платформы, которой у плюсов все равно нет.
И самое главное,
>охватывающем максимум возможностей (низоуровневое программирование, средний и высокий уровни)
Такие попытки я вижу в C++ и считаю их утопией. По мне, так лучше бы C++ оставили без изменений, а вся эта энергия была бы направлена на создание свободной альтернативы C# с повышенной гибкостью в области управления памятью (хотя бы с профилями GC, а лучше с ручным управлением GC). Хотя, конечно, и это утопия. Майкрософт подошел к вопросу по-диктаторски, поэтому у них так изящно и красиво все сделано. Комитетом ничего подобного не создать никогда, а то, что создают отдельные энтузиасты, так и лежит на полке — некому проталкивать.
Здравствуйте, SV., Вы писали:
SV.>А я уже сказал, как философ философу: спасибо, не надо. Это будет уже не один язык, а два, но только называться одинаково и создавать путаницу. Путаницу, типа той, что создают клавиша C/C в QWERTY/ЙЦУКЕН или необоснованное применение UTF-8. Опять какой-нибудь любитель современного сиплюсплюса нахерачит на ровном месте многочисленные typeof, уволится, а ты потом ползай, разгребай. Или скачал библиотеку, а там отражения, которые у тебя не поддерживаются. При том, что код, в принципе, пишется без них. А как быть со стандартными библиотеками? Использовать там отражения? И использовать плохо, и не использовать — глупо.
Скачал библиотеку, а там лямбда-функции, которые у тебя не поддерживаются. Или шаблоны. Или обработка исключений (компилятор Borland C 3.11 насколько я помню не поддерживает ) Что делать???
SV.>Мое мнение: здесь создатели нового стандарта сделали ошибку. reinterpret_cast не нужен потому, что при наличии инструментов для операций с памятью (взятие указателя, приведение типа указателя в C-стиле, разыменование), которые вместе неявно нарушают контракты, он является оператором, который делает это явно. Логики в этом я не вижу. const_cast не нужен потому, что просто не нужен. Приведите пример, когда он нужен, а я попробую переписать код, чтобы он обходился без этого оператора и не был хуже.
Насколько я знаю, reinpterpret_cast введен именно для того, чтобы "потенциально опасные" операции работы с указателями сделать явными и заметными в коде. Но с вашей логикой можно пойти дальше и спросить — а зачем private и protected, если можно взять адрес объекта, привести к какому-нибудь unsigned char* и фигачить в память
А с const_cast я не могу привести никаких примеров (ни положительных ни отрицательных), но я жду, когда кто-то приведет пример когда он нужен. Т.к. сказать "не нужно" можно вообще про что угодно, и никакой полезной информации это не даст, а на реальных примерах можно уже и понять, нужен он или не нужен.
SV.>То же относится к коду с деревом и подбором, про который вы не ответили. Я просто вспоминаю, что в плюсах я никогда не использовал RTTI и отлично себя чувствовал, а в шарпе использовал, конечно, но в таких ситуациях, где все решал динамически-бинарный характер платформы, которой у плюсов все равно нет.
Пример с деревом можно переписать, я знаю: в базовый класс ввести поле тега и соответствующий enum, в дочерних классах инициализировать это поле соответствующими значениями, а в функции поиска родителя нужного типа — искать по этому тегу. Но каждый раз, когда я буду добавлять новый тип, мне нужно будет добавлять тип и в enum, в случае с динамик кастом оно само работает. В этом и смысл рефлексии — если информация есть в компиляторе, зачем заставлять программиста ее прописывать еще раз явно во всяких енумах, массивах и т.п.?
SV.>Такие попытки я вижу в C++ и считаю их утопией. По мне, так лучше бы C++ оставили без изменений, а вся эта энергия была бы направлена на создание свободной альтернативы C# с повышенной гибкостью в области управления памятью (хотя бы с профилями GC, а лучше с ручным управлением GC). Хотя, конечно, и это утопия. Майкрософт подошел к вопросу по-диктаторски, поэтому у них так изящно и красиво все сделано. Комитетом ничего подобного не создать никогда, а то, что создают отдельные энтузиасты, так и лежит на полке — некому проталкивать.
Я примерно такой энтузиаст и именно этим занимаюсь. Just for fun. Получится, не получится — пофиг, но по крайней мере это повод подумать над интересными вопросами.
Здравствуйте, NeoCode, Вы писали:
SV.>>А я уже сказал, как философ философу: спасибо, не надо. Это будет уже не один язык, а два, но только называться одинаково и создавать путаницу. Путаницу, типа той, что создают клавиша C/C в QWERTY/ЙЦУКЕН или необоснованное применение UTF-8. Опять какой-нибудь любитель современного сиплюсплюса нахерачит на ровном месте многочисленные typeof, уволится, а ты потом ползай, разгребай. Или скачал библиотеку, а там отражения, которые у тебя не поддерживаются. При том, что код, в принципе, пишется без них. А как быть со стандартными библиотеками? Использовать там отражения? И использовать плохо, и не использовать — глупо.
NC>Скачал библиотеку, а там лямбда-функции, которые у тебя не поддерживаются. Или шаблоны. Или обработка исключений (компилятор Borland C 3.11 насколько я помню не поддерживает ) Что делать???
Вы говорите о breaking changes в языке. С ними будет одно из двух. Или они входят в заголовочный файл (интерфейс библиотеки), и у вас просто не соберется проект, или они входят в либу, и все будет работать без проблем, а вы об этом просто не узнаете.
А я говорю о том, что вам пришел указатель из либы через самый простейший интерфейс, и вы даже не знаете, что вам на нем вернет typeof и не закрашится ли в этом месте программа. Причем, в принципе.
Разница понятна?
SV.>>Мое мнение: здесь создатели нового стандарта сделали ошибку. reinterpret_cast не нужен потому, что при наличии инструментов для операций с памятью (взятие указателя, приведение типа указателя в C-стиле, разыменование), которые вместе неявно нарушают контракты, он является оператором, который делает это явно. Логики в этом я не вижу. const_cast не нужен потому, что просто не нужен. Приведите пример, когда он нужен, а я попробую переписать код, чтобы он обходился без этого оператора и не был хуже.
NC>Насколько я знаю, reinpterpret_cast введен именно для того, чтобы "потенциально опасные" операции работы с указателями сделать явными и заметными в коде. Но с вашей логикой можно пойти дальше и спросить — а зачем private и protected, если можно взять адрес объекта, привести к какому-нибудь unsigned char* и фигачить в память
Извините, не люблю передергиваний, так что, дальше пас.
Здравствуйте, SV., Вы писали:
SV.>А я говорю о том, что вам пришел указатель из либы через самый простейший интерфейс, и вы даже не знаете, что вам на нем вернет typeof и не закрашится ли в этом месте программа. Причем, в принципе.
SV.>Разница понятна?
Почему закрашится? При правильной реализации просто вернет null и все. Другое дело, что такое поведение должно быть описано в стандарте языка и правильно реализовано в компиляторах. Но это вполне реально.
Это тоже что и с любыми интерфейсами (COM-объекты всякие и т.п.): в одной версии там нет какого-то интерфейса, в следующей версии добавили. Нет — значит нет, получаем нуль.
Здравствуйте, NeoCode, Вы писали:
NC>Я пытаюсь понять, нужен ли const_cast. Не в С++, а в некотором идеальном си-подобном языке программирования, охватывающем максимум возможностей (низоуровневое программирование, средний и высокий уровни). Он действительно нарушает контракт, но в С++ почему-то его сделали отдельно от reinterpret_cast, который нарушает все контракты по определению. Вот пытаюсь понять, почему. NC>Наверное, для константности следует создать отдельную тему, так как оказывается что там не все так просто.
const_cast нужен для того, чтобы нарушать контракт строго определённым образом. Так же, как static_cast — другим строго определённым способом (приведение вниз по иерархии).
Сам дизайн этих кастов, безусловно, навеян языком Си. Поэтому я предпочитаю велосипеды вида
// T выводитсяtemplate<class T> T& unconst_ref(T const& t) { return const_cast<T&>(t); }
template<class T> T* unconst_ptr(T const* t) { return const_cast<T*>(t); }
// To указывается, From выводитсяtemplate<class To, class From> To& downcast_ref(From& t) { static_assert(is_base_and_derived<To,From>::value); return static_cast<To&>(t); }
template<class To, class From> To& downcast_ptr(From* t) { static_assert(is_base_and_derived<To,From>::value); return static_cast<To*>(t); }
С учётом сдвига базы, даункаст и реинтерпрет могут дать разные результаты.
Вот зачем два разных конста — тут можно поспорить.
Сколько бы ни было константностей, их всегда можно законным образом сломать через алиасы
struct Foo
{
void change();
bool unchanged() const;
};
Foo* global;
void f(Foo const& x) // обещаем, что не будем менять x
{
global->change(); // нагло соврали!
}
void test_f()
{
Foo foo;
global = &foo;
assert(foo.unchanged());
f(foo);
assert(foo.unchanged()); // увы
}
class Bar
{
Foo const* member;
public:
explicit Bar(Foo const* m) : member(m) {}
Foo const* see_but_not_change() const { return member; }
bool unchanged() const { return member->unchanged(); }
};
Foo the_foo;
const const const Bar the_bar(&the_foo); // клянёмся, что Bar неизмененvoid test_b()
{
assert(the_bar.unchanged());
the_foo.change(); // пробрались во внутреннее состояние и сломали его
assert(the_bar.unchanged()); // увы
}