Зачем использовать static_cast <int> (x) вместо (int) x?

474

Я слышал, что функция static_cast должна быть предпочтительней для C-стиля или простого кастомизации в стиле функции. Это правда? Почему?

  • 31
    Возражаю вашу честь, спрашиваю и отвечаю .
  • 20
    Я не согласен, этот другой вопрос касался описания различий между приведениями типов в C ++. Этот вопрос о реальной полезности static_cast, который немного отличается.
Показать ещё 6 комментариев
Теги:
casting

9 ответов

481
Лучший ответ

Основная причина заключается в том, что классические касты C не делают различий между тем, что мы называем static_cast<>(), reinterpret_cast<>(), const_cast<>() и dynamic_cast<>(). Эти четыре вещи совершенно разные.

A static_cast<>() обычно безопасен. Существует допустимое преобразование на языке или соответствующий конструктор, который делает это возможным. Единственный раз, когда это немного рискованно, когда вы бросаете в унаследованный класс; вы должны убедиться, что объект на самом деле является потомком, который, по вашему утверждению, есть, посредством внешнего языка (например, флаг в объекте). A dynamic_cast<>() является безопасным до тех пор, пока проверяется результат (указатель) или возможное исключение принимается во внимание (ссылка).

A reinterpret_cast<>() (или a const_cast<>()), с другой стороны, всегда опасно. Вы говорите компилятору: "Поверьте мне: я знаю, что это не похоже на foo (похоже, что он не изменяет), но это".

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

Предположим, что:

class CMyClass : public CMyBase {...};
class CMyOtherStuff {...} ;

CMyBase  *pSomething; // filled somewhere

Теперь эти два скомпилируются таким же образом:

CMyClass *pMyObject;
pMyObject = static_cast<CMyClass*>(pSomething); // Safe; as long as we checked

pMyObject = (CMyClass*)(pSomething); // Same as static_cast<>
                                     // Safe; as long as we checked
                                     // but harder to read

Однако посмотрим на этот почти идентичный код:

CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Compiler error: Can't convert

pOther = (CMyOtherStuff*)(pSomething);            // No compiler error.
                                                  // Same as reinterpret_cast<>
                                                  // and it wrong!!!

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

Вторая проблема заключается в том, что приведение в стиле C слишком сложно найти. В сложных выражениях может быть очень сложно увидеть стили C-стиля. Практически невозможно написать автоматизированный инструмент, в котором необходимо найти приводы C-стиля (например, инструмент поиска) без полномасштабного интерфейса компилятора С++. С другой стороны, легко найти "static_cast <" или "reinterpret_cast <".

pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
      // No compiler error.
      // but the presence of a reinterpret_cast<> is 
      // like a Siren with Red Flashing Lights in your code.
      // The mere typing of it should cause you to feel VERY uncomfortable.

Это означает, что не только C-style casts более опасны, но намного сложнее найти их всех, чтобы убедиться, что они верны.

  • 27
    Вы не должны использовать static_cast для приведения иерархии наследования, а скорее dynamic_cast . Это вернет либо нулевой указатель, либо действительный указатель.
  • 38
    @ Дэвид Торнли: Я согласен, как правило. Я думаю, что указал предостережения для использования static_cast в этой ситуации. dynamic_cast может быть безопаснее, но это не всегда лучший вариант. Иногда вы знаете, что указатель указывает на данный подтип, непрозрачный для компилятора, и static_cast работает быстрее. По крайней мере, в некоторых средах dynamic_cast требует дополнительной поддержки компилятора и стоимости времени выполнения (включая RTTI), и вы можете не захотеть включать его только для пары проверок, которые вы можете выполнить самостоятельно. RTTI в C ++ - это только одно из возможных решений проблемы.
Показать ещё 22 комментария
86

Один прагматический совет: вы можете легко искать ключевое слово static_cast в исходном коде, если планируете убрать проект.

  • 3
    Вы также можете искать, используя скобки, такие как "(int)", но хороший ответ и веская причина использовать приведение в стиле C ++.
  • 4
    @Mike, который найдет ложные срабатывания - объявление функции с одним параметром int .
Показать ещё 2 комментария
41

Короче:

  • static_cast<>() дает вам возможность проверки времени компиляции, C-Style литье не делает.
  • static_cast<>() можно легко заметить где-нибудь внутри исходного кода на С++; в отличие от C_Style литье сложнее обнаружить.
  • Намерения передаются гораздо лучше с использованием С++-трансляций.

Подробнее:

Статическое преобразование выполняет преобразования между совместимыми типами. Это похож на C-стиль, но более ограничительный. Например, приведение в стиле C позволит целочисленному указателю указывать на char.

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

Так как это приводит к 4-байтовому указателю, указывающему на 1 байт выделенного память, запись на этот указатель вызовет ошибку во время выполнения или перезапишет некоторую соседнюю память.

*p = 5; // run-time error: stack corruption

В отличие от стилей в стиле C статическая броска позволит компилятор для проверки того, что типы данных указателя и указателя совместимый, что позволяет программисту поймать эту ошибку назначение указателя во время компиляции.

int *q = static_cast<int*>(&c); // compile-time error

Дополнительная информация:
В чем разница между static_cast < > и литью стиля C
и
Обычный листинг против static_cast vs. dynamic_cast

  • 15
    Я не согласен с тем, что static_cast<>() более читабелен. Я имею в виду, иногда это так, но в большинстве случаев - особенно для базовых целочисленных типов - это просто ужасно и излишне многословно. Например: это функция, которая заменяет байты 32-битного слова. Было бы почти невозможно прочитать с использованием приведений static_cast<uint##>() , но это довольно легко понять с помощью приведений (uint##) . Изображение кода: imgur.com/NoHbGve
  • 2
    @ToddLehman: Спасибо, но я тоже не always говорил. (но в большинстве случаев да) Есть определенные случаи, когда приведение стиля c более читабельно. Это одна из причин того, что кастинг в стиле c все еще жив и крут в c ++ imho. :) Кстати, это был очень хороший пример
Показать ещё 1 комментарий
25

Вопрос больше, чем просто использовать wither static_cast или C style casting, потому что есть разные вещи, которые случаются при использовании стилей стиля C. Операторы литья С++ предназначены для того, чтобы сделать эти операции более явными.

На поверхности static_cast и стили стиля C выглядят одинаково, например, при переносе одного значения в другое:

int i;
double d = (double)i;                  //C-style cast
double d2 = static_cast<double>( i );  //C++ cast

Оба из них передают целочисленное значение в double. Однако при работе с указателями все усложняется. некоторые примеры:

class A {};
class B : public A {};

A* a = new B;
B* b = (B*)a;                                  //(1) what is this supposed to do?

char* c = (char*)new int( 5 );                 //(2) that weird?
char* c1 = static_cast<char*>( new int( 5 ) ); //(3) compile time error

В этом примере (1) возможно ОК, потому что объект, на который указывает A, действительно является экземпляром B. Но что, если вы не знаете в тот момент кода, что на самом деле указывает? (2) может быть совершенно законным (вам нужно только посмотреть на один байт целого числа), но это также может быть ошибкой, и в этом случае ошибка будет приятной, например (3). Операторы литья С++ предназначены для выявления этих проблем в коде, предоставляя при этом ошибки времени компиляции или времени выполнения.

Итак, для строгой "кавычки значений" вы можете использовать static_cast. Если вы хотите, чтобы полиморфное литье указателей во время выполнения использовало dynamic_cast. Если вы действительно хотите забыть о типах, вы можете использовать reintrepret_cast. И просто выкинуть const из окна есть const_cast.

Они просто делают код более явным, чтобы он выглядел так, будто вы знаете, что вы делаете.

19

static_cast означает, что вы не можете случайно const_cast или reinterpret_cast, что хорошо.

  • 4
    Дополнительные (хотя и довольно незначительные) преимущества по сравнению с кастом в стиле C заключаются в том, что он выделяется больше (делать что-то потенциально плохое должно выглядеть некрасиво) и более удобен.
  • 4
    grep-Способность всегда плюс в моей книге.
7

Это о том, какую степень безопасности вы хотите навязать.

Когда вы пишете (bar) foo (что эквивалентно reinterpret_cast<bar> foo, если вы не предоставили оператор преобразования типа), вы говорите компилятору, чтобы игнорировать безопасность типа и просто делайте, как сказано.

Когда вы пишете static_cast<bar> foo, вы просите компилятор, по крайней мере, проверить, что преобразование типа имеет смысл и для интегральных типов вставить код преобразования.


EDIT 2014-02-26

Я написал этот ответ более 5 лет назад, и я понял, что это неправильно. (См. Комментарии.) Но он по-прежнему получает upvotes!

  • 7
    (bar) foo не эквивалентно reinterpret_cast <bar> (foo). Правила для "(TYPE) expr" заключаются в том, что он выберет подходящий тип стиля C ++ для использования, который может включать reinterpret_cast.
  • 0
    Хорошая точка зрения. Евро Мичелли дал однозначный ответ на этот вопрос.
7
  • Позволяет легко найти отливки в ваш код с использованием grep или аналогичного инструменты.
  • Указывает, какой вид   вы делаете, и занимаетесь   компилятор поможет в его обеспечении.   Если вы хотите только отбросить   const-ness, то вы можете использовать   const_cast, который не позволит вам   делать другие типы конверсий.
  • Каста по своей природе уродливы - вы как       программист бросает вызов тому, как       компилятор обычно обрабатывал ваши       код. Вы говорите       компилятор: "Я знаю лучше тебя".       В этом случае имеет смысл       что выполнение броска должно быть       умеренно болезненное дело, и       что они должны       кода, поскольку они являются вероятным источником       проблем.

См. Эффективный С++ Введение

  • 0
    Я полностью согласен с этим для классов, но имеет ли смысл использовать приведение стиля C ++ для типов POD?
  • 0
    Я думаю так. Все 3 причины относятся к POD, и полезно иметь только одно правило, а не отдельные правила для классов и POD.
Показать ещё 1 комментарий
4

C Стили стиля легко пропустить в блоке кода. Стили стиля С++ - это не только лучшая практика; они обеспечивают гораздо большую степень гибкости.

reinterpret_cast допускает интеграцию с преобразованиями типа указателя, однако может быть небезопасным при неправильном использовании.

static_cast предлагает хорошее преобразование для числовых типов, например. от enum до int или ints до float или любых типов данных, которые вы уверены в типе. Он не выполняет проверку времени выполнения.

dynamic_cast, с другой стороны, выполнит эти проверки, помечая любые неоднозначные назначения или преобразования. Он работает только с указателями и ссылками и несет накладные расходы.

Есть несколько других, но это основные из них, с которыми вы столкнетесь.

3

static_cast, помимо манипулирования указателями на классы, также может использоваться для выполнения преобразований, явно определенных в классах, а также для выполнения стандартных преобразований между основными типами:

double d = 3.14159265;
int    i = static_cast<int>(d);
  • 3
    Зачем кому-то писать static_cast<int>(d) , хотя (int)d гораздо более кратко и читабельно? (Я имею в виду базовые типы, а не указатели объектов.)
  • 0
    @ToddLehman: последовательность
Показать ещё 4 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню