Регулярное литье против static_cast vs. dynamic_cast

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

MyClass *m = (MyClass *)ptr;

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

MyClass *m = (MyClass *)ptr;
MyClass *m = static_cast<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);

Ответ 1

static_cast

static_cast используется для случаев, когда вы в основном хотите обратить вспять неявное преобразование, с несколькими ограничениями и дополнениями. static_cast не выполняет проверки времени выполнения. Это следует использовать, если вы знаете, что ссылаетесь на объект определенного типа, и поэтому проверка не нужна. Пример:

void func(void *data) {
  // Conversion from MyClass* -> void* is implicit
  MyClass *c = static_cast<MyClass*>(data);
  ...
}

int main() {
  MyClass c;
  start_thread(&func, &c)  // func(&c) will be called
      .join();
}

В этом примере вы знаете, что вы передали объект MyClass, и, таким образом, нет необходимости в проверке времени выполнения.

dynamic_cast

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

if (JumpStm *j = dynamic_cast<JumpStm*>(&stm)) {
  ...
} else if (ExprStm *e = dynamic_cast<ExprStm*>(&stm)) {
  ...
}

Вы не можете использовать dynamic_cast, если вы опускаете (отливаете в производный класс), а тип аргумента не является полиморфным. Например, следующий код недействителен, поскольку Base не содержит никакой виртуальной функции:

struct Base { };
struct Derived : Base { };
int main() {
  Derived d; Base *b = &d;
  dynamic_cast<Derived*>(b); // Invalid
}

"up-cast" (отбрасывается в базовый класс) всегда действителен как с static_cast, так и dynamic_cast, а также без каких-либо бросков, так как "up-cast" является неявным преобразованием.

Обычный ролик

Эти роли также называются C-style cast. Листинг C-стиля в основном идентичен тестированию ряда последовательностей С++-трансляций, а также выполнение первого слияния С++, которое не учитывает dynamic_cast. Излишне говорить, что это намного мощнее, поскольку он объединяет все const_cast, static_cast и reinterpret_cast, но также небезопасно, потому что он не использует dynamic_cast.

Кроме того, приведение в стиле C не только позволяет вам это делать, но также позволяет безопасно применять к частному базовому классу, тогда как "эквивалентная" последовательность static_cast даст вам ошибку времени компиляции для этого.

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

Ответ 2

Статический литье

Статический литье выполняет преобразования между совместимыми типами. Он похож на 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

Реинтерпрет-литье

Чтобы принудительно преобразовать указатель, так же как и в стиле C-стиля в фоновом режиме, вместо него будет использоваться реинтерпрет.

int *r = reinterpret_cast<int*>(&c); // forced conversion

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

Динамический литье

Этот используется только для преобразования указателей объектов и ссылок на объекты в другие указатели или ссылочные типы в иерархии наследования. Это единственный актер, который гарантирует, что объект, на который указывает, может быть преобразован, выполнив проверку времени выполнения, указатель ссылается на полный объект целевого типа. Для того чтобы эта проверка времени выполнения была возможной, объект должен быть полиморфным. То есть класс должен определять или наследовать хотя бы одну виртуальную функцию. Это связано с тем, что компилятор будет генерировать необходимую информацию типа времени выполнения для таких объектов.

Динамические примеры литья

В приведенном ниже примере указатель MyChild преобразуется в указатель MyBase с использованием динамического переноса. Это преобразование с производной базой выполняется успешно, поскольку объект Child включает в себя полный базовый объект.

class MyBase 
{ 
  public:
  virtual void test() {}
};
class MyChild : public MyBase {};



int main()
{
  MyChild *child = new MyChild();
  MyBase  *base = dynamic_cast<MyBase*>(child); // ok
}

Следующий пример пытается преобразовать указатель MyBase в указатель MyChild. Поскольку объект Base не содержит полного объекта Child, это преобразование указателя не будет выполнено. Чтобы указать это, динамический кадр возвращает нулевой указатель. Это дает удобный способ проверить, удалось ли преобразование во время выполнения.

MyBase  *base = new MyBase();
MyChild *child = dynamic_cast<MyChild*>(base);


if (child == 0) 
std::cout << "Null pointer returned";

Если ссылка преобразуется вместо указателя, динамическая трансляция затем завершится неудачей, выставив исключение bad_cast. Это нужно обрабатывать с помощью инструкции try-catch.

#include <exception>
// …  
try
{ 
  MyChild &child = dynamic_cast<MyChild&>(*base);
}
catch(std::bad_cast &e) 
{ 
  std::cout << e.what(); // bad dynamic_cast
}

Динамический или статический литье

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

MyBase *base = static_cast<MyBase*>(child); // ok

Однако во втором примере преобразование может быть успешным или неудачным. Он не сработает, если объект MyBase содержит экземпляр MyBase, и он будет успешным, если он содержит экземпляр MyChild. В некоторых ситуациях это может быть неизвестно до времени выполнения. Когда это так, динамический бросок - лучший выбор, чем статический бросок.

// Succeeds for a MyChild object
MyChild *child = dynamic_cast<MyChild*>(base);

Если преобразование base-to-производное было выполнено с использованием статического броска вместо динамического переноса, преобразование не потерпело бы неудачу. Он вернул бы указатель, который ссылался на неполный объект. Выделение такого указателя может привести к ошибкам во время выполнения.

// Allowed, but invalid
MyChild *child = static_cast<MyChild*>(base);

// Incomplete MyChild object dereferenced
(*child);

Консоль

Это прежде всего используется для добавления или удаления модификатора константы переменной.

const int myConst = 5;
int *nonConst = const_cast<int*>(&myConst); // removes const

Несмотря на то, что const cast позволяет изменять значение константы, это все равно недействительный код, который может вызвать ошибку во время выполнения. Это может произойти, например, если константа была расположена в секции постоянной памяти.

*nonConst = 10; // potential run-time error

Const cast вместо этого используется в основном, когда есть функция, которая принимает аргумент указателя non-constant, даже если он не изменяет pointee.

void print(int *p) 
{
   std::cout << *p;
}

Затем функция может быть передана константной переменной, используя const const.

print(&myConst); // error: cannot convert 
                 // const int* to int*

print(nonConst); // allowed

Источник и дополнительные пояснения

Ответ 3

Вы должны посмотреть на статью С++ Programming/Type Casting.

В нем содержится хорошее описание всех типов броска. Из приведенной выше ссылки:

const_cast

const_cast (выражение) const_cast < > () используется для добавления/удаления const (ness) (или изменчивость) переменной.

static_cast

static_cast (выражение) static_cast < > () используется для целые типы. 'например.' char → long, int- > short и т.д.

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

dynamic_cast

Динамическое преобразование используется для преобразования указателей и ссылок во время выполнения, обычно для целей литья указателя или ссылки вверх или вниз цепочка наследования (иерархия наследования).

dynamic_cast (выражение)

Тип цели должен быть указателем или ссылочным типом, а выражение должно оцениваться указателем или ссылкой. Динамические работы только когда тип объекта, к которому относится выражение, является совместимый с типом цели, а базовый класс имеет по крайней мере один виртуальная функция-член. Если нет, и тип выражаемого выражения является указателем, возвращается NULL, если динамическое нажатие ссылки fail, генерируется исключение bad_cast. Когда это не сработает, динамический cast возвращает указатель или ссылку целевого типа на объект к которому относится выражение.

reinterpret_cast

Reinterpret cast просто отличает один тип поразрядным другим. Любой указатель или интегральный тип могут быть отлиты для любого другого с реинтерпрет-литой, легко разрешая неправильное использование. Например, при повторном толковании может, небезопасно, указать целочисленный указатель на указатель строки.

Ответ 4

FYI, я считаю, что Bjarne Stroustrup цитируется, что нужно избегать приведения в стиле C и что вы должны использовать static_cast или dynamic_cast, если это вообще возможно.

Часто задаваемые вопросы по стилю Barne Stroustrup С++

Возьмите этот совет за то, что пожелаете. Я далек от того, чтобы быть гуру С++.

Ответ 5

Избегайте использования стилей C-Style.

C-style casts - это сочетание const и reinterpret cast, и трудно найти и заменить в коде. Программист С++ должен избегать приведения в стиле C.

Ответ 6

C-style casts conflate const_cast, static_cast и reinterpret_cast.

Я хочу, чтобы у С++ не было C-style cast. Отливки С++ выделяются должным образом (как и должно быть, касты обычно указывают на что-то плохое) и правильно различают различные виды конверсий, которые выполняются. Они также позволяют записывать аналогичные функции, например. boost:: lexical_cast, что довольно приятно с точки зрения согласованности.

Ответ 7

dynamic_cast имеет проверку типа времени выполнения и работает только со ссылками и указателями, тогда как static_cast не предлагает проверку типа времени выполнения. Для получения полной информации см. Статью MSDN static_cast Operator.

Ответ 8

dynamic_cast поддерживает только указатели и ссылочные типы. Он возвращает NULL, если трансляция невозможна, если тип является указателем или генерирует исключение, если тип является ссылочным типом. Следовательно, dynamic_cast может использоваться для проверки того, является ли объект данного типа, static_cast не может (вы просто получите недопустимое значение).

C-style (и другие) приведения были рассмотрены в других ответах.