Прочитав здесь много ответов о приведении C-стиля в C++, у меня все еще есть один маленький вопрос. Могу ли я использовать приведение в стиле C для встроенных типов, таких как long x=(long)y;
, или это все еще считается плохим и опасным?
Можно ли использовать приведение в стиле C для встроенных типов?
Ответы (9)
Я бы не стал по следующим причинам:
- Приведения типов уродливы и должны быть уродливыми и выделяться в вашем коде, и их можно будет найти с помощью grep и подобных инструментов.
- «Всегда использовать приведения C++» — это простое правило, которое с большей вероятностью запомнят и будут соблюдать, чем «Используйте приведения C++ для пользовательских типов, но можно использовать приведения в стиле C для встроенных типов».
- Приведения в стиле C++ предоставляют другим разработчикам больше информации о том, почему приведение необходимо.
- Приведения в стиле C могут позволить вам выполнять преобразования, которые вы не планировали, - если у вас есть интерфейс, который принимает (int*), и вы использовали приведения в стиле C, чтобы передать ему const int*, а интерфейс изменился, чтобы принять в длинном * ваш код, использующий приведения в стиле C, будет продолжать работать, даже если это не то, что вы хотели.
Могу ли я использовать приведение в стиле C для встроенных типов, таких как long x=(long)y; или это все же считается плохим и опасным?
Не используйте их, никогда. Причины против их использования применимы и здесь. По сути, как только вы их используете, все ставки сняты, потому что компилятор вам больше не поможет. Хотя это более опасно для указателей, чем для других типов, оно потенциально все еще опасно и дает плохую диагностику компилятора в случае ошибок, тогда как приведения новых стилей предлагают более подробные сообщения об ошибках, поскольку их использование более ограничено: Мейерс приводит пример отбрасывания const
ness: использование любого приведения, кроме const_cast
, не будет компилироваться, поэтому становится ясно, что здесь происходит.
Кроме того, некоторые другие недостатки применимы независимо от типов, а именно синтаксические соображения: Приведение в стиле C очень ненавязчиво. Это нехорошо: приведения C++ четко выделяются в коде и указывают на потенциально опасный код. Их также можно легко найти в IDE и текстовых редакторах. Попробуйте поискать приведение в стиле C в большом коде, и вы увидите, насколько это сложно.
С другой стороны, приведения в стиле C не имеют преимуществ перед приведениями в C++, так что не стоит даже рассматривать компромисс.
В более общем плане Скотт Мейерс советует «свести к минимуму приведения типов» в Effective C++ (статья 27), потому что «приведения разрушают систему типов».
int convertDoubleToInt(double)
, подрывает систему типов. Но это в значительной степени то, что делает static_cast<int>(myDouble)
. Что подрывает систему типов, так это const_cast
и reinterpret_cast
, но не два других приведения.
- person Ruslan; 03.07.2017
static_cast<int>(some_double)
не разрушает систему типов. Но static_cast
вообще может ниспровергнуть систему типов, например, путем приведения void*
к T*
(это ниспровергает систему типов, поскольку не выполняет преобразование значений; скорее, он утверждает что-то, что система типов проверить не могу). Для конверсий я настоятельно рекомендую вместо этого использовать синтаксис T{obj}
, так как это делает намерение более ясным. Увы, это тоже не надежно (std::vector<…>{}
не выполняет преобразование…).
- person Konrad Rudolph; 03.07.2017
T{obj}
для выполнения сужающего преобразования, не так ли?
- person Ruslan; 03.07.2017
T{}
для не сужающих конверсий. Сужающие конверсии, вероятно, заслуживают static_cast
, чтобы выделить их в любом случае.
- person Konrad Rudolph; 03.07.2017
Если вы выполняете приведение от числового типа к к другому числовому типу, то я думаю, что приведения в стиле C предпочтительнее, чем *_cast
. У каждого из операторов *_cast
есть определенная причина не использовать его для числовых типов:
reinterpret_cast
применительно к числовым типам выполняет обычное числовое преобразование, а не переинтерпретирует биты, т. е. записьreinterpret_cast<uint64_t>(3.14159)
не создает целое число с тем же битовым представлением, что и эта константа с плавающей запятой. Это противоречит интуиции.const_cast
никогда не требуется для числового значения, и если применяется к числовой переменной (в отличие от указателя или ссылки на число), предполагает, что тип этой переменной это неверно.dynamic_cast
просто не имеет смысла для чисел, потому что числа никогда не имеют динамического типа.static_cast
обычно используется для типов классов. Поэтому выглядит странно применятьstatic_cast
к числу; ваши читатели будут чесать затылки и задаваться вопросом, есть ли что-то, чего они не знают оstatic_cast
, что делает его отличным от приведения в стиле C применительно к числу. (На самом деле они идентичны, но мне пришлось пару раз прочитать соответствующий раздел спецификации C++, чтобы убедиться, что они идентичны, и у меня все еще возникает реакция «здесь должно быть что-то странное» .)
и есть дополнительная стилистическая причина избегать их:
Если вам нужно выполнить приведение числовых типов, вам, вероятно, потребуется сделать несколько из них в кластере; из-за этого важна краткость приведения C-стиля. Например, программа, которую я сейчас пишу, имеет целую кучу таких вещей:
uint32_t n = ((uint32_t(buf[0]) << 24) | (uint32_t(buf[1]) << 16) | (uint32_t(buf[2]) << 8) | (uint32_t(buf[3]) ));
Использование здесь
static_cast
скрыло бы арифметику, что очень важно. (Примечание: эти приведения не нужны, только еслиsizeof(uint32_t) <= sizeof(unsigned int)
, что является безопасным предположением в настоящее время, но я все же предпочитаю явность.) (Да, я, вероятно, должен выделить эту операцию во встроенный помощникbe32_to_cpu
, но Я бы все равно закодировал это так же.)
static_cast
для преобразования между числовыми типами.
- person zwol; 04.09.2013
reinterpret_cast
как приведения в стиле C. Это произойдет, если тип, который вы приводите, будет указателем, который, как вы думали, был целочисленным типом. Хм, написав, что я чувствую, что немного хватаюсь за соломинку ... до того, как мы начали это обсуждение, я думал, что есть больше потенциальных ошибок программиста, которые можно было бы поймать при приведении к числовому типу. Теперь я думаю, что случай, который я только что описал, единственный.
- person PeterSW; 05.09.2013
static_cast
обычно используется для типов классов. Поэтому кажется странным применять static_cast
к числу; Это звучит как личное ожидание и не похоже на какие-либо факты.
- person François Andrieux; 11.11.2020
На мой взгляд, приведение встроенных типов в стиле C при использовании стандартных библиотечных функций и STL допустимо и приводит к более легкому чтению кода.
В компании, в которой я работаю, мы компилируем с максимальными (уровня 4) предупреждениями, поэтому мы получаем предупреждения о каждом маленьком приведении типа и т. д. Поэтому я использую в них приведения в стиле c, потому что они маленькие, не такие подробные и имеют смысл. .
for (int i = 0; i < (int)myvec.size(); i++)
{
// do something int-related with i
}
float val = (float)atof(input_string);
и т.д....
Но если это (например, библиотечный) код, который может измениться, то static_cast‹>() лучше, потому что вы можете гарантировать, что компилятор выдаст ошибку, если типы изменятся, и приведение больше не имеет смысла. Кроме того, невозможно искать приведения в коде, если вы используете только c-стиль. «static_cast<mytype>(
» довольно легко найти. :)
Я могу придумать одно законное использование приведения в стиле C:
// cast away return value to shut up pedantic compiler warnings
(void)printf("foo\n");
Я думаю, что это может быть нормально, учитывая контекст. Пример:
/* Convert -1..1 to -32768..32767 */
short float_to_short(float f)
{
return (short)(max(-32768.f, min(32767.f, f * 32767.f)));
}
Зачем вам нужен именно этот актерский состав? Любой числовой тип можно преобразовать в длинный без приведения (с потенциальной потерей точности), поэтому приведение не позволяет компилятору делать то, что он уже не может. Выполняя приведение, все, что вы делаете, — это лишаете компилятор возможности предупреждать о потенциальной проблеме. Если вы конвертируете какой-то другой базовый тип (например, указатель) в длинный, мне бы очень хотелось увидеть реинтерпретацию_приведения ‹>, а не приведение C-типа, чтобы я мог легко найти, что происходит, если окажется, что быть проблемой.
Я не одобряю кастинг без причины, и уж точно не одобряю кастинг С-типа без веской причины. Я не вижу причин для приведения типов между большинством встроенных типов, и если есть один, я хочу иметь возможность легко найти его с помощью текстового поиска.
Единственный случай, когда я предпочитаю устаревшее приведение C, — это приведение байтовых буферов к другой подписи. Многие API имеют разные соглашения, и на самом деле нет «правильного ответа», и приведение не опасно в том контексте, в котором оно выполняется, где код должен быть настолько независимым от платформы, насколько и комбинация используемых библиотек.
Конкретный пример того, что я имею в виду, я думаю, что это просто отлично:
unsigned char foo[16];
lib1_load_foo(key);
lib2_use_foo((char*)key);
Но для чего-либо еще, если требуется приведение, оно будет иметь потенциальные побочные эффекты, которые должны выделяться, и использование приведения в стиле C++ (возможно, уродливое) является правильным выбором. И если приведение не требуется, то не используйте приведение.
Если вы обнаружите, что вам нужно выполнить приведение в стиле C (или reinterpret_cast), внимательно посмотрите на свой код, и на 99,99% вы убедитесь, что с ним что-то не так. Оба эти приведения почти неизбежно приводят к специфическому (и очень часто неопределенному) поведению, связанному с реализацией.