Я пишу код C и C ++ почти двадцать лет, но есть один аспект этих языков, который я никогда не понимал. Я явно использовал регулярные слепки, т.е.
MyClass *m = (MyClass *)ptr;
повсюду, но, кажется, есть два других типа слепков, и я не знаю разницы. В чем разница между следующими строками кода?
MyClass *m = (MyClass *)ptr;
MyClass *m = static_cast<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);
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
, и, следовательно, нет необходимости проверять время выполнения, чтобы убедиться в этом.
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-стилем. Состав в стиле C в основном идентичен опробованию ряда последовательностей слепков C ++ и взятию первого работающего приведения C ++, даже не принимая во внимание «dynamic_cast». Излишне говорить, что это гораздо мощнее, поскольку объединяет все const_cast
, static_cast
и reinterpret_cast
, но это также небезопасно, поскольку не использует dynamic_cast
.
Кроме того, приведения в стиле C не только позволяют вам это сделать, но и позволяют безопасно выполнять приведение в частный базовый класс, в то время как последовательность «эквивалент» static_cast
выдаст вам ошибку времени компиляции для этого.
Некоторые люди предпочитают слепки в стиле C из-за их краткости. Я использую их только для числовых слепков и использую соответствующие слепки C ++, когда задействованы определенные пользователем типы, поскольку они обеспечивают более строгую проверку.
Статический состав выполняет преобразования между совместимыми типами. Это похоже на C-стиль, но более ограничительно. Например, приведение в стиле 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
Этот состав обрабатывает преобразования между определенными несвязанными типами, такими как один тип указателя к другому несовместимому типу указателя. Он просто выполнит двоичную копию данных без изменения базового битового шаблона. Обратите внимание, что результат такой низкоуровневой операции зависит от системы и, следовательно, не переносим. Его следует использовать с осторожностью, если его нельзя избежать вообще.
< h2 > Динамический состав < / h2 >Этот используется только для преобразования указателей объектов и ссылок объектов в другие типы указателей или ссылок в иерархии наследования. Это единственный состав, который гарантирует, что указанный объект может быть преобразован, выполняя проверку времени выполнения, когда указатель ссылается на полный объект типа назначения. Чтобы эта проверка времени выполнения была возможной, объект должен быть полиморфным. То есть класс должен определять или наследовать хотя бы одну виртуальную функцию. Это связано с тем, что компилятор будет генерировать только необходимую информацию типа времени выполнения для таких объектов.
Динамические примеры актеров
В приведенном ниже примере указатель 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. Это должно быть обработано с помощью заявления об улове.
#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
Хотя const cast позволяет изменять значение константы, это все еще недействительный код, который может вызвать ошибку во время выполнения. Это может произойти, например, если константа находится в разделе памяти только для чтения.
*nonConst = 10; // potential run-time error
Вместо этого Const cast используется главным образом, когда есть функция, которая принимает аргумент непостоянного указателя, даже если он не изменяет pointee.
void print(int *p)
{
std::cout << *p;
}
Затем функция может быть передана постоянной переменной с помощью последовательного приведения.
print(&myConst); // error: cannot convert
// const int* to int*
print(nonConst); // allowed
Вам следует взглянуть на статью C ++ Programming / Type Casting .
Он содержит хорошее описание всех различных типов актеров. Следующее взято из вышеуказанной ссылки:
const_cast
const_cast < T > (выражение) const_cast < > () используется для добавления / удаления const (ness) (или летучесть) переменной.
static_cast
static_cast < T > (выражение) Static_cast < > () используется для приведения между ними целочисленные типы. например.'char- > long, int- > short и т. д.
Статический состав также используется для наведения указателей на связанные типы пример литья недействительным * к соответствующему типу.
динамический_cast
Динамический состав используется для преобразования указателей и ссылок во время выполнения как правило, с целью наведения указателя или ссылки вверх или вниз цепочка наследования (иерархия наследования).
Dynamic_cast < target type > (выражение)
Тип цели должен быть указателем или типом ссылки, и выражение должно оцениваться по указателю или ссылке. Динамический актерский состав работает только когда тип объекта, к которому относится выражение совместим с целевым типом, а базовый класс имеет хотя бы один функция виртуального участника. Если нет, и тип выражения приведение это указатель, возвращается NULL, если динамический состав на ссылке не удается, исключение bad_cast брошено. Когда это не терпит неудачу, динамично Приведение возвращает указатель или ссылку типа цели на объект на какое выражение ссылаются.
reinterpret_cast
Переинтерпретировать бросок просто бросает один тип побитно в другой. Любой указатель или интегральный тип может быть отлит к любому другому с помощью реинтерпрета легко допускать злоупотребления. Например, с реинтерпретом отлита одна может, небезопасно, навести целочисленный указатель на строковый указатель.
Избегайте использования приведений C-Style.
Приведения в стиле C представляют собой смесь последовательности и реинтерпретации, и в вашем коде трудно найти и заменить. Программист приложений C ++ должен избегать кастовых форм в стиле C.
К вашему сведению, я полагаю, что Бьярне Страуструп говорит, что следует избегать слепков в стиле C и что вы должны использовать static_cast или Dynamic_cast, если это вообще возможно.
Часто задаваемые вопросы о стиле Барна Страуструпа на C ++
Примите этот совет за то, что вы будете. Я далеко не гуру C ++.
Приливы в стиле C объединяют const_cast, static_cast и reinterpret_cast.
Я бы хотел, чтобы у C ++ не было слепков в стиле C. C ++ отливает правильно (как и должно быть; слепки обычно указывают на что-то плохое) и правильно различают различные виды преобразования, которые выполняют слепки. Они также позволяют писать аналогичные функции, например,. boost :: lexical_cast, что довольно неплохо с точки зрения согласованности.
dynamic_cast
имеет проверку типа во время выполнения и работает только со ссылками и указателями, тогда как static_cast
не предлагает проверку типа во время выполнения. Для получения полной информации см. Статью MSDN [static_cast Operator][1] .
[1]: http://msdn.microsoft.com/en-us/library/c36yw7x9 (VS.80).aspx
dynamic_cast
поддерживает только указатели и типы ссылок. Возвращает NULL
, если приведение невозможно, если тип является указателем или выдает исключение, если тип является ссылочным типом. Следовательно, dynamic_cast
может использоваться для проверки того, относится ли объект к заданному типу, static_cast
не может (вы просто получите недопустимое значение).
C-стиль (и другие) слепки были освещены в других ответах.