Можно ли использовать приведение в стиле C для встроенных типов?

Прочитав здесь много ответов о приведении C-стиля в C++, у меня все еще есть один маленький вопрос. Могу ли я использовать приведение в стиле C для встроенных типов, таких как long x=(long)y;, или это все еще считается плохим и опасным?


person jackhab    schedule 09.02.2009    source источник


Ответы (9)


Я бы не стал по следующим причинам:

  • Приведения типов уродливы и должны быть уродливыми и выделяться в вашем коде, и их можно будет найти с помощью grep и подобных инструментов.
  • «Всегда использовать приведения C++» — это простое правило, которое с большей вероятностью запомнят и будут соблюдать, чем «Используйте приведения C++ для пользовательских типов, но можно использовать приведения в стиле C для встроенных типов».
  • Приведения в стиле C++ предоставляют другим разработчикам больше информации о том, почему приведение необходимо.
  • Приведения в стиле C могут позволить вам выполнять преобразования, которые вы не планировали, - если у вас есть интерфейс, который принимает (int*), и вы использовали приведения в стиле C, чтобы передать ему const int*, а интерфейс изменился, чтобы принять в длинном * ваш код, использующий приведения в стиле C, будет продолжать работать, даже если это не то, что вы хотели.
person JohnMcG    schedule 09.02.2009
comment
Я не верю, что какая-либо из этих причин (кроме вероятно первой) применима к приведению числовых типов; см. мой ответ. - person zwol; 07.09.2012

Могу ли я использовать приведение в стиле C для встроенных типов, таких как long x=(long)y; или это все же считается плохим и опасным?

Не используйте их, никогда. Причины против их использования применимы и здесь. По сути, как только вы их используете, все ставки сняты, потому что компилятор вам больше не поможет. Хотя это более опасно для указателей, чем для других типов, оно потенциально все еще опасно и дает плохую диагностику компилятора в случае ошибок, тогда как приведения новых стилей предлагают более подробные сообщения об ошибках, поскольку их использование более ограничено: Мейерс приводит пример отбрасывания constness: использование любого приведения, кроме const_cast, не будет компилироваться, поэтому становится ясно, что здесь происходит.

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

С другой стороны, приведения в стиле C не имеют преимуществ перед приведениями в C++, так что не стоит даже рассматривать компромисс.

В более общем плане Скотт Мейерс советует «свести к минимуму приведения типов» в Effective C++ (статья 27), потому что «приведения разрушают систему типов».

person Konrad Rudolph    schedule 09.02.2009
comment
Правда о минимизации бросков. А если серьезно, можете ли вы указать причину, по которой не следует использовать float y = 1.0; двойной х = двойной (у)? Потому что это было вопросом. Этот бросок не опасен и, вероятно, не должен отвлекать по сравнению с настоящими опасными (которые вам почти никогда не понадобятся, если вы сделаете это правильно).. - person ypnos; 14.02.2009
comment
Что ж, в этом очень конкретном случае приведение даже не требуется, поскольку тип double больше, чем тип float. - person Raphaël Saint-Pierre; 17.04.2009
comment
Я не верю, что какая-либо из этих причин (кроме того, что возможно должно быть уродливым, чтобы выделяться, с чем я в целом не согласен) применима к приведениям среди числовых типов; см. мой ответ. - person zwol; 07.09.2012
comment
@ Зак Нет. Но гораздо лучше иметь универсально применимое простое правило, чем правило с одним странным исключением. Тем более, что нет ничего плохого в том, чтобы просто всегда применять рекомендации. - person Konrad Rudolph; 08.09.2012
comment
Я предполагаю, что у нас есть фундаментальное философское разногласие: я не верю в универсально применимые простые правила. Вернее, я считаю, что они всегда в конце концов оказываются неверными. Я подробно перечислил недостатки применения приведения новых стилей к числам в своем ответе. - person zwol; 08.09.2012
comment
@Zack Это не недоразумение: я знаю, что простых правил не существует. Именно по этой причине нам нужно максимально упростить. В C++ важно максимально минимизировать приведения типов. Тем не менее, ваш пост делает несколько хороших моментов. Здесь есть +1, чтобы противодействовать -1. - person Konrad Rudolph; 08.09.2012
comment
бросает подорвать систему типов, звучит довольно странно. Я бы не сказал, что такая функция, как int convertDoubleToInt(double), подрывает систему типов. Но это в значительной степени то, что делает static_cast<int>(myDouble). Что подрывает систему типов, так это const_cast и reinterpret_cast, но не два других приведения. - person Ruslan; 03.07.2017
comment
@Ruslan Я цитирую здесь Скотта Мейерса. Это правда, что конкретный случай static_cast<int>(some_double) не разрушает систему типов. Но static_cast вообще может ниспровергнуть систему типов, например, путем приведения void* к T* (это ниспровергает систему типов, поскольку не выполняет преобразование значений; скорее, он утверждает что-то, что система типов проверить не могу). Для конверсий я настоятельно рекомендую вместо этого использовать синтаксис T{obj}, так как это делает намерение более ясным. Увы, это тоже не надежно (std::vector<…>{} не выполняет преобразование…). - person Konrad Rudolph; 03.07.2017
comment
Вы не можете использовать T{obj} для выполнения сужающего преобразования, не так ли? - person Ruslan; 03.07.2017
comment
@Руслан Нет. Оплошность с моей стороны. Я бы по-прежнему выступал за 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, но Я бы все равно закодировал это так же.)

person zwol    schedule 07.09.2012
comment
Ваше утверждение, что static_cast идентично приведениям в стиле c, неверно. Приведение в стиле C может разрешаться как reinterpret_cast, const_cast или static_cast в зависимости от ситуации. - person PeterSW; 04.09.2013
comment
Приведение @PeterSW в стиле C идентично static_cast для преобразования между числовыми типами. - person zwol; 04.09.2013
comment
Если вы выполняете приведение к целочисленному типу, который может содержать указатель, то использование static_cast может спасти вас от случайного скрытия reinterpret_cast как приведения в стиле C. Это произойдет, если тип, который вы приводите, будет указателем, который, как вы думали, был целочисленным типом. Хм, написав, что я чувствую, что немного хватаюсь за соломинку ... до того, как мы начали это обсуждение, я думал, что есть больше потенциальных ошибок программиста, которые можно было бы поймать при приведении к числовому типу. Теперь я думаю, что случай, который я только что описал, единственный. - person PeterSW; 05.09.2013
comment
@PeterSW Мои рекомендации здесь применимы только тогда, когда и источник и место назначения являются однозначно числовыми типами в контексте. Если потенциально задействован указатель, я согласен, что операторы приведения в новом стиле безопаснее. - person zwol; 05.09.2013
comment
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>(» довольно легко найти. :)

person user379529    schedule 29.06.2010

Я могу придумать одно законное использование приведения в стиле C:

// cast away return value to shut up pedantic compiler warnings
(void)printf("foo\n");
person tragomaskhalos    schedule 08.07.2009

Я думаю, что это может быть нормально, учитывая контекст. Пример:

/* Convert -1..1 to -32768..32767 */
short float_to_short(float f)
{
    return (short)(max(-32768.f, min(32767.f, f * 32767.f)));
}
person Johan Kotlinski    schedule 13.02.2009

Зачем вам нужен именно этот актерский состав? Любой числовой тип можно преобразовать в длинный без приведения (с потенциальной потерей точности), поэтому приведение не позволяет компилятору делать то, что он уже не может. Выполняя приведение, все, что вы делаете, — это лишаете компилятор возможности предупреждать о потенциальной проблеме. Если вы конвертируете какой-то другой базовый тип (например, указатель) в длинный, мне бы очень хотелось увидеть реинтерпретацию_приведения ‹>, а не приведение C-типа, чтобы я мог легко найти, что происходит, если окажется, что быть проблемой.

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

person David Thornley    schedule 09.02.2009
comment
Если вы не хотите, чтобы компилятор генерировал ошибки потери точности, необходимо выполнить приведение - person Michael Platings; 10.02.2009
comment
Конечно, но, возможно, вам лучше подавить их на данный момент. Когда мы перешли к 64-битной обработке (требовалось больше места в памяти), мы бы потеряли точность в некоторых случаях (включая size_t), если бы выполняли приведение типов. - person David Thornley; 10.02.2009

Единственный случай, когда я предпочитаю устаревшее приведение C, — это приведение байтовых буферов к другой подписи. Многие API имеют разные соглашения, и на самом деле нет «правильного ответа», и приведение не опасно в том контексте, в котором оно выполняется, где код должен быть настолько независимым от платформы, насколько и комбинация используемых библиотек.

Конкретный пример того, что я имею в виду, я думаю, что это просто отлично:

unsigned char foo[16];
lib1_load_foo(key);
lib2_use_foo((char*)key);

Но для чего-либо еще, если требуется приведение, оно будет иметь потенциальные побочные эффекты, которые должны выделяться, и использование приведения в стиле C++ (возможно, уродливое) является правильным выбором. И если приведение не требуется, то не используйте приведение.

person hyde    schedule 11.03.2013

Если вы обнаружите, что вам нужно выполнить приведение в стиле C (или reinterpret_cast), внимательно посмотрите на свой код, и на 99,99% вы убедитесь, что с ним что-то не так. Оба эти приведения почти неизбежно приводят к специфическому (и очень часто неопределенному) поведению, связанному с реализацией.

person Community    schedule 09.02.2009
comment
Если вы солжете компилятору, он вас накажет. Эти слепки либо ложь, либо попытка исправить предыдущую ложь. - person Darron; 09.02.2009
comment
В исследовательском коде я часто вижу работу с float и double бок о бок или, например. int данные из изображения преобразуются в float/double для дальнейшей обработки. Это реальные варианты использования приведения, и я думаю, что они составляют более 0,01%. - person ypnos; 14.02.2009
comment
Как насчет параметров для потоковых функций в Windows? Вы должны пройти void*. Распространенным шаблоном является передача reinterpret_cast‹void*›(this). - person Dave Mooney; 17.04.2009
comment
Утверждения, подобные этому ответу, заставляют меня задаться вопросом, писали ли люди когда-либо настоящий код. Приведение типов между типами с плавающей запятой (даже если вы пытаетесь избежать их, потому что они ужасно медленные), знаковыми и беззнаковыми целыми числами, целыми числами и любыми ужасными типами, которые возвращает vector::size() в наши дни, и т. д. абсолютно неизбежны и совершенно нормальны в реальный, практичный код, особенно когда вы склеиваете библиотеки с API-интерфейсами, находящимися вне вашего контроля. - person user168715; 14.04.2015