Я пишу код на C и C++ почти двадцать лет, но есть один аспект этих языков, который я никогда не понимал. Я, очевидно, использовал обычные приведения, т.е.
MyClass *m = (MyClass *)ptr;
повсюду, но, похоже, есть два других типа слепков, и я не знаю разницы. В чем разница между следующими строками кода?
MyClass *m = (MyClass *)ptr;
MyClass *m = static_cast<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);
как вопрос с ответом может быть дубликатом вопроса без ответа ?? более того, этот вопрос был задан раньше, чем "оригинал"
@Vladp Если вам все еще интересно, или кто-то еще читает это и задается вопросом. (Кроме того, для записи, это закрыл не модератор, а пользователь с тупица)
К вашему сведению, связанный вопрос имеет гораздо больше голосов, и ответы также имеют гораздо больше голосов. Также связанный вопрос имеет хорошие нетеоретические примеры. (Кроме того, связанный вопрос не относится к синтаксису приведения типов в стиле C как «обычное приведение».)





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, которое просто слепо пробует несколько типов приведения, пока не сработает что-либо, независимо от смысла ... хороший.
Избегайте использования приведений в стиле C.
Приведение в стиле C представляет собой сочетание константного и переинтерпретируемого приведения, и его сложно найти и заменить в вашем коде. Программисту C++-приложений следует избегать приведения в стиле C.
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 полезен, когда вы не знаете, каков динамический тип объекта. Он возвращает нулевой указатель, если упомянутый объект не содержит тип, приведенный к базовому классу (при приведении к ссылке в этом случае выдается исключение 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/…
@ JohannesSchaub-litb: Вы уверены, что приведение в стиле C позволяет вам «безопасно» преобразовать его в частный базовый класс? Я вижу, что это работает, когда частный базовый класс является единственным / base /, но как насчет виртуального / множественного наследования? Я предполагаю, что приведение в стиле C не выполняет манипуляции с указателем.
@ JohannesSchaub-litb, правда ли, что есть некоторые накладные расходы, связанные с использованием старых приведений в стиле c вместо приведений C++?
Вы уверены, что можете написать MyClass *c = static_cast<MyClass*>(data);, когда данные - (void*)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
@Joseph: Ваше предположение, что «приведение стиля C не выполняет манипуляции с указателем» неверно.
@BenVoigt: Вы уверены? Я смутно помню, как читал, что одна из опасностей приведения стиля C по сравнению с static_cast заключалась в том, что он не выполнял арифметические операции с указателями при работе с виртуальным наследованием.
@Joseph: Он не будет правильно выполнять перекрестное приведение или любой другой случай, когда требуется проверка времени выполнения (требуется dynamic_cast). Но он будет выполнять те же настройки указателя, что и static_cast. Множественное (не виртуальное) наследование прекрасно поддерживается, и будет использоваться правильная настройка указателя.
Как прокомментировал @xcrypt, я также хотел бы знать, есть ли накладные расходы при использовании обычного приведения стиля C.
@ JohannesSchaub-litb, вам следует добавить кое-что о кросс-кастинге и объяснить, как происходит это. Я бы сделал новый ответ, но он получил столько голосов, что было бы очень полезно добавить его здесь.
Приведение в стиле @haxpor в стиле C не имеет накладных расходов на динамическое приведение - оно может выполнять настройку указателя, которая в основном представляет собой просто добавление или вычитание указателя. Однако он будет вызывать встроенные (intfloat) и определяемые пользователем преобразования, последнее из которых может быть сколь угодно сложным.
Не могли бы вы более подробно объяснить, почему понижающее преобразование в разделе динамического приведения недопустимо? Предположим, у Derived есть member m, которого я хочу достичь, как бы этого достичь, если dynamic_cast не подходит?
@ JohannesSchaub-litb, а как насчет времени литья c-type? Он медленнее, чем static_cast?
Можем ли мы иметь раздел о вариациях, которые мы получаем, когда указатели на базовые / производные смещаются указателем виртуальной таблицы? также такое смещение во время приведения происходит с функциями-членами, иногда нулевой указатель становится 1.
Можете ли вы добавить больше деталей в «Кроме того, приведение типов в стиле C не только позволяет вам это делать, но и позволяет безопасно выполнять приведение к частному базовому классу, в то время как« эквивалентная »последовательность static_cast даст вам возможность компиляции -время ошибки для этого. " ?
Согласно en.cppreference.com/w/cpp/language/explicit_cast, приведение в стиле C - это тип приведения типа Только, который вызывает определенные пользователем операторы преобразования. Так ли это? Если да, то я предлагаю включить эту информацию в ваш (очень хороший) ответ.
«static_cast не выполняет проверки во время выполнения» - я считаю, что он должен проверять во время выполнения, чтобы nullptr всегда вел себя правильно, не так ли?
dynamic_cast поддерживает только указатели и ссылочные типы. Он возвращает NULL, если приведение невозможно, если тип является указателем, или выдает исключение, если тип является ссылочным. Следовательно, dynamic_cast можно использовать для проверки того, принадлежит ли объект заданному типу, а static_cast - нет (вы просто получите недопустимое значение).
Приведения в стиле C (и другие) были рассмотрены в других ответах.
«вы просто получите недопустимое значение» и неопределенное поведение. То есть программа вела себя неправильно, даже если вы не использовали значение
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 переменной.
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>
от ребенка к базе, гипс не требуется: MyBase *base = child; // ok
На мой взгляд, лучший ответ, очень простой и понятный
Я бы не назвал устаревшее приведение в стиле C «обычным приведением» в C++, потому что это совсем не так. Как правило, вы не должны использовать C++, особенно с классами, с ним слишком легко ошибиться. Его использование - признак того, что программист на C перешел на C++, но еще не совсем изучил C++.