Обычное приведение против static_cast против dynamic_cast

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

MyClass *m = (MyClass *)ptr;

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

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

Я бы не назвал устаревшее приведение в стиле C «обычным приведением» в C++, потому что это совсем не так. Как правило, вы не должны использовать C++, особенно с классами, с ним слишком легко ошибиться. Его использование - признак того, что программист на C перешел на C++, но еще не совсем изучил C++.

hyde 30.01.2013 11:03

как вопрос с ответом может быть дубликатом вопроса без ответа ?? более того, этот вопрос был задан раньше, чем "оригинал"

Vladp 21.06.2015 11:28

@Vladp Если вам все еще интересно, или кто-то еще читает это и задается вопросом. (Кроме того, для записи, это закрыл не модератор, а пользователь с тупица)

Fund Monica's Lawsuit 01.02.2017 04:16

К вашему сведению, связанный вопрос имеет гораздо больше голосов, и ответы также имеют гораздо больше голосов. Также связанный вопрос имеет хорошие нетеоретические примеры. (Кроме того, связанный вопрос не относится к синтаксису приведения типов в стиле C как «обычное приведение».)

Trevor Boyd Smith 07.03.2018 17:20
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1 823
4
751 084
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

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

Вам стоит посмотреть статью Программирование на C++ / приведение типов.

Он содержит хорошее описание всех различных типов приведения. Следующее взято из приведенной выше ссылки:

const_cast

const_cast(expression) The const_cast<>() is used to add/remove const(ness) (or volatile-ness) of a variable.

static_cast

static_cast(expression) The static_cast<>() is used to cast between the integer types. 'e.g.' char->long, int->short etc.

Static cast is also used to cast pointers to related types, for example casting void* to the appropriate type.

dynamic_cast

Dynamic cast is used to convert pointers and references at run-time, generally for the purpose of casting a pointer or reference up or down an inheritance chain (inheritance hierarchy).

dynamic_cast(expression)

The target type must be a pointer or reference type, and the expression must evaluate to a pointer or reference. Dynamic cast works only when the type of object to which the expression refers is compatible with the target type and the base class has at least one virtual member function. If not, and the type of expression being cast is a pointer, NULL is returned, if a dynamic cast on a reference fails, a bad_cast exception is thrown. When it doesn't fail, dynamic cast returns a pointer or reference of the target type to the object to which expression referred.

reinterpret_cast

Reinterpret cast simply casts one type bitwise to another. Any pointer or integral type can be casted to any other with reinterpret cast, easily allowing for misuse. For instance, with reinterpret cast one might, unsafely, cast an integer pointer to a string pointer.

Приведения в стиле C объединяют const_cast, static_cast и reinterpret_cast.

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

К вашему сведению, я считаю, что цитируется Бьярн Страуструп, который говорит, что следует избегать приведений в стиле C и что вы должны использовать static_cast или dynamic_cast, если это вообще возможно.

Часто задаваемые вопросы о стиле C++ Барна Страуструпа

Следуйте этому совету, как хотите. Я далек от гуру C++.

^ Да, потому что приведение типов C++, которые явно помечены и намеренно ограничены четко определенными ролями, более "адски", чем приведение типов C, которое просто слепо пробует несколько типов приведения, пока не сработает что-либо, независимо от смысла ... хороший.

underscore_d 13.04.2016 13:22

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

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

Ответ принят как подходящий

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
}

«Приведение вверх» (приведение к базовому классу) всегда допустимо как для static_cast, так и для dynamic_cast, а также без какого-либо приведения, поскольку «преобразование вверх» является неявным преобразованием.

Обычный актерский состав

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

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

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

См. Также два дополнительных каста буста: boost.org/doc/libs/1_47_0/libs/conversion/…

Neil G 10.08.2011 21:31

@ JohannesSchaub-litb: Вы уверены, что приведение в стиле C позволяет вам «безопасно» преобразовать его в частный базовый класс? Я вижу, что это работает, когда частный базовый класс является единственным / base /, но как насчет виртуального / множественного наследования? Я предполагаю, что приведение в стиле C не выполняет манипуляции с указателем.

Joseph Garvin 29.02.2012 22:32

@ JohannesSchaub-litb, правда ли, что есть некоторые накладные расходы, связанные с использованием старых приведений в стиле c вместо приведений C++?

xcrypt 10.05.2012 23:47

Вы уверены, что можете написать MyClass *c = static_cast<MyClass*>(data);, когда данные - (void*)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?

Stephane Rolland 01.12.2012 15:08

@Joseph: Ваше предположение, что «приведение стиля C не выполняет манипуляции с указателем» неверно.

Ben Voigt 29.12.2012 00:15

@BenVoigt: Вы уверены? Я смутно помню, как читал, что одна из опасностей приведения стиля C по сравнению с static_cast заключалась в том, что он не выполнял арифметические операции с указателями при работе с виртуальным наследованием.

Joseph Garvin 29.12.2012 00:30

@Joseph: Он не будет правильно выполнять перекрестное приведение или любой другой случай, когда требуется проверка времени выполнения (требуется dynamic_cast). Но он будет выполнять те же настройки указателя, что и static_cast. Множественное (не виртуальное) наследование прекрасно поддерживается, и будет использоваться правильная настройка указателя.

Ben Voigt 29.12.2012 01:16

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

haxpor 07.02.2013 19:29

@ JohannesSchaub-litb, вам следует добавить кое-что о кросс-кастинге и объяснить, как происходит это. Я бы сделал новый ответ, но он получил столько голосов, что было бы очень полезно добавить его здесь.

Stephen Lin 03.03.2013 06:24

Приведение в стиле @haxpor в стиле C не имеет накладных расходов на динамическое приведение - оно может выполнять настройку указателя, которая в основном представляет собой просто добавление или вычитание указателя. Однако он будет вызывать встроенные (intfloat) и определяемые пользователем преобразования, последнее из которых может быть сколь угодно сложным.

Stephen Lin 03.03.2013 06:26

Не могли бы вы более подробно объяснить, почему понижающее преобразование в разделе динамического приведения недопустимо? Предположим, у Derived есть member m, которого я хочу достичь, как бы этого достичь, если dynamic_cast не подходит?

ted 09.03.2013 04:06

@ JohannesSchaub-litb, а как насчет времени литья c-type? Он медленнее, чем static_cast?

klm123 08.11.2013 19:13

Можем ли мы иметь раздел о вариациях, которые мы получаем, когда указатели на базовые / производные смещаются указателем виртуальной таблицы? также такое смещение во время приведения происходит с функциями-членами, иногда нулевой указатель становится 1.

v.oddou 10.04.2015 08:35

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

Boyko Perfanov 28.05.2015 16:36

Согласно en.cppreference.com/w/cpp/language/explicit_cast, приведение в стиле C - это тип приведения типа Только, который вызывает определенные пользователем операторы преобразования. Так ли это? Если да, то я предлагаю включить эту информацию в ваш (очень хороший) ответ.

alecov 13.08.2016 02:53

«static_cast не выполняет проверки во время выполнения» - я считаю, что он должен проверять во время выполнения, чтобы nullptr всегда вел себя правильно, не так ли?

Drew Dormann 17.03.2017 19:39

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

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

«вы просто получите недопустимое значение» и неопределенное поведение. То есть программа вела себя неправильно, даже если вы не использовали значение

curiousguy 03.11.2019 03:39

Статическое приведение

The static cast performs conversions between compatible types. It is similar to the C-style cast, but is more restrictive. For example, the C-style cast would allow an integer pointer to point to a 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 с использованием динамического преобразования. Это преобразование производного в базовое завершается успешно, поскольку дочерний объект включает полный базовый объект.

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. Поскольку базовый объект не содержит полного дочернего объекта, преобразование указателя завершится ошибкой. Чтобы указать это, динамическое приведение возвращает нулевой указатель. Это дает удобный способ проверить, удалось ли преобразование во время выполнения.

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);

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

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

Const cast

Он в основном используется для добавления или удаления модификатора const переменной.

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

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

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

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

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

Затем функции можно передать постоянную переменную с помощью константного приведения.

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

Источник и другие пояснения

std::bad_cast определен в <typeinfo>

Evg 17.04.2017 20:19

от ребенка к базе, гипс не требуется: MyBase *base = child; // ok

Isidoro Ghezzi 07.09.2018 13:07

На мой взгляд, лучший ответ, очень простой и понятный

Mohammed Noureldin 22.07.2019 11:21

Другие вопросы по теме