Для некоторых полезных конкретных примеров использования разного рода приведений вы можете проверить первый ответ на похожий вопрос в этой другой теме .
TeaMonkie
2
Вы можете найти действительно хорошие ответы на свой вопрос выше. Но я бы хотел добавить еще один момент, @ e.James: «Нет ничего, что эти новые операторы приведения типов в c ++ могут сделать, а в стиле c - нет. Они добавлены более или менее для лучшей читаемости кода».
BreakBadSP
@BreakBadSP Новые версии предназначены не только для лучшей читаемости кода. Они там, чтобы было сложнее делать опасные вещи, такие как отбрасывание констант или разыгрывание указателей вместо их значений. У static_cast гораздо меньше возможностей сделать что-то опасное, чем в стиле AC!
FourtyTwo
@FourtyTwo согласился
BreakBadSP
Ответы:
2572
static_castэто первый каст, который вы должны попытаться использовать. Он выполняет такие вещи, как неявные преобразования между типами (например, intto floatили указатель на void*), и может также вызывать функции явного преобразования (или неявные). Во многих случаях явное указание static_castне требуется, но важно отметить, что T(something)синтаксис эквивалентен (T)somethingи его следует избегать (подробнее об этом позже). Однако A T(something, something_else)безопасен и гарантированно вызывает конструктор.
static_castможет также приводиться через иерархии наследования. Это не нужно при приведении вверх (к базовому классу), но при приведении вниз его можно использовать до тех пор, пока оно не преобразуется через virtualнаследование. Однако он не выполняет проверку, и это неопределенное поведение - static_castприводить иерархию к типу, который на самом деле не является типом объекта.
const_castможет использоваться для удаления или добавления constк переменной; никакой другой C ++ cast не способен удалить его (даже не reinterpret_cast). Важно отметить, что изменение прежнего constзначения не определено, только если исходная переменная имеет значение const; если вы используете его для constудаления ссылки на то, что не было объявлено const, это безопасно. Это может быть полезно при перегрузке функций-членов const, например, на основе. Его также можно использовать для добавления constк объекту, например, для вызова перегрузки функции-члена.
const_castтакже работает аналогично volatile, хотя это менее распространено.
dynamic_castисключительно используется для обработки полиморфизма. Вы можете привести указатель или ссылку на любой полиморфный тип к любому другому типу класса (полиморфный тип имеет как минимум одну виртуальную функцию, объявленную или унаследованную). Вы можете использовать его не только для того, чтобы бросать вниз - вы можете кастовать вбок или даже на другую цепь. dynamic_castБудет искать нужный объект и вернуть его , если это возможно. Если он не может, он вернется nullptrв случае указателя или выбросит std::bad_castв случае ссылки.
dynamic_castимеет некоторые ограничения, хотя. Это не работает, если в иерархии наследования есть несколько объектов одного типа (так называемый «страшный бриллиант»), и вы не используете virtualнаследование. Она также может идти только через публичное наследование - это всегда будет не в путешествие через protectedили privateнаследования. Однако это редко является проблемой, поскольку такие формы наследования встречаются редко.
reinterpret_castэто самый опасный состав, и должен использоваться очень экономно. Он превращает один тип непосредственно в другой - например, приведение значения от одного указателя к другому или сохранение указателя в int, или всякие другие неприятные вещи. В основном, единственная гарантия, которую вы получаете, reinterpret_castзаключается в том, что обычно, если вы приведете результат обратно к исходному типу, вы получите точно такое же значение (но не в том случае, если промежуточный тип меньше исходного типа). Есть ряд преобразований, которые reinterpret_castтоже не могут сделать. Он используется главным образом для особенно странных преобразований и битовых манипуляций, таких как превращение потока необработанных данных в реальные данные или хранение данных в младших битах указателя на выровненные данные.
C-стиль литье и функция стиль бросок является слепки с использованием (type)objectили type(object), соответственно, и функционально эквивалентны. Они определены как первое из следующего, которое успешно:
const_cast
static_cast (хотя игнорируя ограничения доступа)
static_cast (см. выше), затем const_cast
reinterpret_cast
reinterpret_cast, тогда const_cast
Поэтому в некоторых случаях он может использоваться в качестве замены для других приведений, но может быть чрезвычайно опасным из-за способности переходить в a reinterpret_cast, и последнее следует отдавать предпочтение, когда требуется явное приведение, если вы не уверены, что static_castэто удастся или reinterpret_castне удастся , Даже тогда рассмотрим более длинный и более явный вариант.
Приведения типа C также игнорируют управление доступом при выполнении a static_cast, что означает, что они имеют возможность выполнять операции, которые не могут выполнять другие приведения. Хотя это в основном клудж, и, на мой взгляд, это просто еще одна причина избегать бросков в стиле C.
dynamic_cast предназначен только для полиморфных типов. вам нужно использовать его только при приведении к производному классу. static_cast, безусловно, является первым вариантом, если вам не нужна особенность dynamic_cast. Это не какой-то чудесный «бросок проверки типа» из серебряной пули вообще.
jalf
2
Отличный ответ! Одно быстрое замечание: static_cast может понадобиться для приведения иерархии в случае, если у вас есть Derived * & для приведения в Base * &, поскольку двойные указатели / ссылки не приводят к автоматическому приведению иерархии. Я сталкивался с такой (честно говоря, не совсем обычной) ситуацией две минуты назад. ;-)
bartgol
5
* "никакой другой C ++ cast не способен удалить const(даже не reinterpret_cast)" ... правда? Как насчет reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))?
user541686 15.01.15
29
Я думаю, что важная деталь, отсутствующая выше, заключается в том, что dynamic_cast имеет снижение производительности во время выполнения по сравнению со static или reinterpret_cast. Это важно, например, в программном обеспечении реального времени.
jfritz42
5
Возможно, стоит упомянуть, что reinterpret_castэто часто является предпочтительным оружием при работе с набором непрозрачных типов данных API
Class Skeleton
333
Используется dynamic_castдля преобразования указателей / ссылок в иерархии наследования.
Используйте static_castдля обычных преобразований типов.
Используйте reinterpret_castдля низкоуровневой реинтерпретации битовых комбинаций. Используйте с особой осторожностью.
Используйте const_castдля изгнания const/volatile. Избегайте этого, если только вы не застряли с использованием некорректного API-интерфейса.
Будьте осторожны с dynamic_cast. Он опирается на RTTI и не будет работать должным образом через границы общих библиотек. Просто потому, что вы создаете исполняемую и совместно используемую библиотеку независимо друг от друга, не существует стандартизированного способа синхронизации RTTI между различными сборками. По этой причине в библиотеке Qt существует qobject_cast <>, который использует информацию о типе QObject для проверки типов.
user3150128
198
(Много теоретического и концептуального объяснения было дано выше)
Ниже приведены некоторые практические примеры, когда я использовал static_cast , dynamic_cast , const_cast , reinterpret_cast .
OnEventData(void* pData){......// pData is a void* pData, // EventData is a structure e.g. // typedef struct _EventData {// std::string id;// std:: string remote_id;// } EventData;// On Some Situation a void pointer *pData// has been static_casted as // EventData* pointer EventData*evtdata =static_cast<EventData*>(pData);.....}
Теория некоторых других ответов хороша, но все еще сбивает с толку, видя эти примеры после прочтения других ответов, действительно все они имеют смысл. То есть без примеров я все еще был не уверен, но с ними я теперь уверен в том, что означают другие ответы.
Solx
1
О последнем использовании reinterpret_cast: это не то же самое, что использование static_cast<char*>(&val)?
Лоренцо Белли
3
@LorenzoBelli Конечно нет. Вы пробовали это? Последний не является допустимым C ++ и блокирует компиляцию. static_castработает только между типами с определенными преобразованиями, видимым отношением по наследству или с / на void *. Для всего остального есть другие забросы. reinterpret castлюбому char *типу разрешено читать представление любого объекта - и это единственный из случаев, когда это ключевое слово полезно, а не безудержный генератор поведения реализации / неопределенности. Но это не считается «нормальным» преобразованием, поэтому не допускается (обычно) очень консервативным static_cast.
underscore_d
2
reinterpret_cast довольно распространен, когда вы работаете с системным программным обеспечением, таким как базы данных. В большинстве случаев вы пишете свой собственный менеджер страниц, который не знает, какой тип данных хранится на странице, и просто возвращает пустой указатель. Это до более высоких уровней, чтобы сделать переосмысление броска и сделать вывод, что они хотят.
Sohaib
1
Пример const_cast демонстрирует неопределенное поведение. Переменная, объявленная как const, не может быть отменена. Тем не менее, переменная, объявленная как неконстантная, которая передается функции, принимающей константную ссылку, может в этой функции быть деконстантной без UB.
Иоганн Герелл
99
Это может помочь, если вы знаете немного внутренностей ...
static_cast
Компилятор C ++ уже знает, как преобразовывать типы масштабирования, такие как float, в int. Используйте static_castдля них.
Когда вы просите компилятор преобразовать тип Aв B, static_castвызывается Bконструктор, передаваемый Aкак param. В качестве альтернативы, Aможет иметь оператор преобразования (т.е. A::operator B()). Если Bтакого конструктора Aнет или у вас нет оператора преобразования, вы получите ошибку времени компиляции.
Преобразование из A*в B*всегда выполняется успешно, если A и B находятся в иерархии наследования (или void), в противном случае вы получите ошибку компиляции.
Поправка : если вы приведете базовый указатель к производному указателю, но если фактический объект не является действительно производным типом, вы не получите ошибку. Вы получаете плохой указатель и, скорее всего, segfault во время выполнения. То же самое относится и A&к B&.
Попался : приведение от Derived к Base или наоборот создаст новую копию! Для людей, пришедших из C # / Java, это может быть огромным сюрпризом, поскольку в результате получается в основном отрубленный объект, созданный из Derived.
dynamic_cast
dynamic_cast использует информацию о типе среды выполнения, чтобы выяснить, является ли приведение действительным. Например, (Base*)чтобы (Derived*)может потерпеть неудачу , если указатель не на самом деле производного типа.
Это означает, что dynamic_cast очень дорогой по сравнению со static_cast!
Для A*чтобы B*, если бросок недействителен , то dynamic_cast вернет nullptr.
Для A&чтобы , B&если литой недействителен , то dynamic_cast выбросит bad_cast исключения.
В отличие от других приведений, есть накладные расходы времени выполнения.
const_cast
В то время как static_cast может делать не-const-const, он не может идти другим путем. Const_cast может работать в обоих направлениях.
Одним из примеров, где это удобно, является итерация по некоторому контейнеру, set<T>который возвращает свои элементы только как const, чтобы убедиться, что вы не измените его ключ. Однако, если вы хотите изменить неключевые члены объекта, тогда все должно быть в порядке. Вы можете использовать const_cast для удаления константности.
Другой пример, когда вы хотите реализовать, T& SomeClass::foo()а также const T& SomeClass::foo() const. Чтобы избежать дублирования кода, вы можете применить const_cast для возврата значения одной функции из другой.
reinterpret_cast
В основном это говорит о том, что возьмите эти байты в этой ячейке памяти и воспринимайте это как заданный объект.
Например, вы можете загрузить 4 байта с плавающей точкой до 4 байтов с целым числом, чтобы увидеть, как выглядят биты в плавающей точке.
Очевидно, что если данные не соответствуют типу, вы можете получить segfault.
Для этого состава нет накладных расходов времени выполнения.
Я добавил информацию об операторе преобразования, но есть еще несколько вещей, которые также должны быть исправлены, и я не чувствую, что это слишком удобно обновлять. Элементы: 1. If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.Вы получаете UB, который может привести к segfault во время выполнения, если вам повезет. 2. Динамические броски могут также использоваться в перекрестном литье. 3. Const броски могут привести к UB в некоторых случаях. Использование mutableможет быть лучшим выбором для реализации логической константности.
Адриан
1
@ Адриан, ты прав во всех отношениях. Ответ написан для людей с более или менее начальным уровнем, и я не хотел перегружать их всеми остальными осложнениями mutable, кросс-кастингом и т. Д.
Я никогда не использовал reinterpret_cast, и задаюсь вопросом, не пахнет ли случай, который нуждается в этом, плохим дизайном. В кодовой базе, над которой я работаю, dynamic_castмного всего используется. Разница в static_castтом, что dynamic_castпроверка во время выполнения делает (что безопаснее) или не может (больше накладных расходов) то, что вы хотите (см. Msdn ).
Я использовал reintrepret_cast для одной цели - получить биты из двойного (такого же размера, как long на моей платформе).
Иисус Навин
2
reinterpret_cast необходим, например, для работы с COM-объектами. CoCreateInstance () имеет выходной параметр типа void ** (последний параметр), в котором вы передадите указатель, объявленный как, например, «INetFwPolicy2 * pNetFwPolicy2». Для этого вам нужно написать что-то вроде reinterpret_cast <void **> (& pNetFwPolicy2).
Серж Рогач
1
Возможно, есть другой подход, но я использую reinterpret_castдля извлечения фрагментов данных из массива. Например, если у меня есть char*большой буфер, заполненный упакованными двоичными данными, который мне нужно пройти и получить отдельные примитивы разных типов. Примерно так:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
Джеймс Матта
Я никогда не использовал reinterpret_cast, там не очень много применений для этого.
Волшебник китов Пика
Лично я видел когда-либо только reinterpret_castпо одной причине. Я видел необработанные данные объекта, хранящиеся в типе данных «blob» в базе данных, а затем, когда данные извлекаются из базы данных, reinterpret_castиспользуется для преобразования этих необработанных данных в объект.
ImaginaryHuman072889
15
В дополнение к другим ответам, приведенным выше, приведен неочевидный пример, когда static_castэтого недостаточно, так что reinterpret_castэто необходимо. Предположим, есть функция, которая в выходном параметре возвращает указатели на объекты разных классов (которые не разделяют общий базовый класс). Реальный пример такой функции CoCreateInstance()(см. Последний параметр, который на самом деле void**). Предположим, вы запрашиваете определенный класс объекта у этой функции, поэтому заранее знаете тип указателя (что вы часто делаете для COM-объектов). В этом случае вы не можете привести указатель к вашему указателю void**с помощью static_cast: вам нужно reinterpret_cast<void**>(&yourPointer).
В коде:
#include<windows.h>#include<netfw.h>.....INetFwPolicy2* pNetFwPolicy2 =nullptr;
HRESULT hr =CoCreateInstance(__uuidof(NetFwPolicy2),nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),//static_cast<void**>(&pNetFwPolicy2) would give a compile errorreinterpret_cast<void**>(&pNetFwPolicy2));
Однако static_castработает для простых указателей (не указателей на указатели), поэтому приведенный выше код можно переписать, чтобы избежать reinterpret_cast(по цене дополнительной переменной) следующим образом:
Не будет ли это работать что - то вроде &static_cast<void*>(pNetFwPolicy2)вместо static_cast<void**>(&pNetFwPolicy2)?
jp48
9
В то время как другие ответы хорошо описывают все различия между приведениями C ++, я хотел бы добавить краткое замечание, почему вы не должны использовать приведения в стиле C (Type) varи Type(var).
Для начинающих в C ++ приведение в стиле C выглядит как операция надмножества над приведением в C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()), и кто-то может предпочесть их перед приведением C ++ , Фактически, бросок в стиле C - это суперсет, и его можно писать короче.
Основная проблема приведений в стиле C заключается в том, что они скрывают реальные намерения разработчиков. Приведения в стиле C могут выполнять практически все типы приведения: от обычно безопасных приведения, выполняемых static_cast <> () и dynamic_cast <> (), к потенциально опасным приведениям, таким как const_cast <> (), где модификатор const можно удалить, поэтому переменные const может быть изменен и reinterpret_cast <> (), который может даже интерпретировать целочисленные значения для указателей.
Вот образец.
int a=rand();// Random number.int* pa1=reinterpret_cast<int*>(a);// OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.int* pa2=static_cast<int*>(a);// Compiler error.int* pa3=dynamic_cast<int*>(a);// Compiler error.int* pa4=(int*) a;// OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.*pa4=5;// Program crashes.
Основная причина, по которой в язык были добавлены приведения C ++, состояла в том, чтобы позволить разработчику уточнить свои намерения - почему он собирается выполнять это приведение. Используя приведение в стиле C, которое является абсолютно допустимым в C ++, вы делаете свой код менее читаемым и более подверженным ошибкам, особенно для других разработчиков, которые не создавали ваш код. Поэтому, чтобы сделать ваш код более читабельным и явным, вы всегда должны отдавать предпочтение приведениям C ++ по сравнению с C-стилями.
Вот небольшая цитата из книги Бьярна Страуструпа (автора C ++) «Язык программирования C ++, 4-е издание» - стр. 302.
Такое приведение в стиле C гораздо опаснее, чем именованные операторы преобразования, потому что обозначения труднее обнаружить в большой программе, а вид преобразования, предназначенный программистом, не является явным.
Только строка (4) компилируется без ошибок. Только reinterpret_cast может использоваться для преобразования указателя на объект в указатель на любой не связанный тип объекта.
Следует отметить следующее: dynamic_cast не будет работать во время выполнения, однако на большинстве компиляторов он также не будет компилироваться, поскольку в структуре преобразуемого указателя нет виртуальных функций, то есть dynamic_cast будет работать только с полиморфными указателями классов. ,
Когда использовать C ++ cast :
Используйте static_cast в качестве эквивалента приведения типа C, который выполняет преобразование значений, или когда нам нужно явно преобразовать указатель из класса в его суперкласс.
Используйте const_cast, чтобы удалить квалификатор const.
Используйте reinterpret_cast для небезопасных преобразований типов указателей в целочисленные и из других типов указателей и обратно. Используйте это, только если мы знаем, что делаем, и понимаем проблемы псевдонимов.
static_castпо dynamic_castсравнению с reinterpret_castвнутренним видом на downcast / upcast
В этом ответе я хочу сравнить эти три механизма на конкретном примере upcast / downcast и проанализировать, что происходит с базовыми указателями / памятью / сборкой, чтобы дать конкретное понимание того, как они сравниваются.
Я считаю, что это даст хорошую интуицию о том, как эти броски отличаются:
static_cast: делает одно смещение адреса во время выполнения (низкое влияние времени выполнения) и не проверяет безопасность, что даунскейт корректен.
dyanamic_cast: делает то же самое смещение адреса во время выполнения, как static_cast, но также и дорогостоящую проверку безопасности, что обратное преобразование корректно, используя RTTI.
Эта проверка безопасности позволяет вам запрашивать, имеет ли указатель базового класса заданный тип во время выполнения, проверяя возвращение nullptrкоторого указывает на недопустимое снижение.
Поэтому, если ваш код не может проверить это nullptrи выполнить допустимое действие без прерывания, вы должны просто использовать static_castвместо динамического приведения.
Если прерывание - это единственное действие, которое может выполнить ваш код, возможно, вам нужно только включить dynamic_castin in debug builds ( -NDEBUG) и использовать static_castиначе, например, как здесь , чтобы не замедлять ваши быстрые запуски.
reinterpret_cast: ничего не делает во время выполнения, даже смещение адреса. Указатель должен точно указывать на правильный тип, даже базовый класс не работает. Обычно вы этого не хотите, если не задействованы необработанные потоки байтов.
Рассмотрим следующий пример кода:
main.cpp
#include<iostream>struct B1 {
B1(int int_in_b1): int_in_b1(int_in_b1){}virtual~B1(){}void f0(){}virtualint f1(){return1;}int int_in_b1;};struct B2 {
B2(int int_in_b2): int_in_b2(int_in_b2){}virtual~B2(){}virtualint f2(){return2;}int int_in_b2;};struct D :public B1,public B2 {
D(int int_in_b1,int int_in_b2,int int_in_d): B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d){}void d(){}int f2(){return3;}int int_in_d;};int main(){
B2 *b2s[2];
B2 b2{11};
D *dp;
D d{1,2,3};// The memory layout must support the virtual method call use case.
b2s[0]=&b2;// An upcast is an implicit static_cast<>().
b2s[1]=&d;
std::cout <<"&d "<<&d << std::endl;
std::cout <<"b2s[0] "<< b2s[0]<< std::endl;
std::cout <<"b2s[1] "<< b2s[1]<< std::endl;
std::cout <<"b2s[0]->f2() "<< b2s[0]->f2()<< std::endl;
std::cout <<"b2s[1]->f2() "<< b2s[1]->f2()<< std::endl;// Now for some downcasts.// Cannot be done implicitly// error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]// dp = (b2s[0]);// Undefined behaviour to an unrelated memory address because this is a B2, not D.
dp =static_cast<D*>(b2s[0]);
std::cout <<"static_cast<D*>(b2s[0]) "<< dp << std::endl;
std::cout <<"static_cast<D*>(b2s[0])->int_in_d "<< dp->int_in_d << std::endl;// OK
dp =static_cast<D*>(b2s[1]);
std::cout <<"static_cast<D*>(b2s[1]) "<< dp << std::endl;
std::cout <<"static_cast<D*>(b2s[1])->int_in_d "<< dp->int_in_d << std::endl;// Segfault because dp is nullptr.
dp =dynamic_cast<D*>(b2s[0]);
std::cout <<"dynamic_cast<D*>(b2s[0]) "<< dp << std::endl;//std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;// OK
dp =dynamic_cast<D*>(b2s[1]);
std::cout <<"dynamic_cast<D*>(b2s[1]) "<< dp << std::endl;
std::cout <<"dynamic_cast<D*>(b2s[1])->int_in_d "<< dp->int_in_d << std::endl;// Undefined behaviour to an unrelated memory address because this// did not calculate the offset to get from B2* to D*.
dp =reinterpret_cast<D*>(b2s[1]);
std::cout <<"reinterpret_cast<D*>(b2s[1]) "<< dp << std::endl;
std::cout <<"reinterpret_cast<D*>(b2s[1])->int_in_d "<< dp->int_in_d << std::endl;}
Теперь, как уже упоминалось по адресу: https://en.wikipedia.org/wiki/Virtual_method_table , для эффективной поддержки вызовов виртуальных методов структура данных в памяти Dдолжна выглядеть примерно так:
B1:+0: pointer to virtual method table of B1
+4: value of int_in_b1
B2:+0: pointer to virtual method table of B2
+4: value of int_in_b2
D:+0: pointer to virtual method table of D (for B1)+4: value of int_in_b1
+8: pointer to virtual method table of D (for B2)+12: value of int_in_b2
+16: value of int_in_d
Ключ в том, что структура данных памяти Dсодержит внутри него структуру памяти совместима с B1и из B2внутренне.
Поэтому мы приходим к критическому выводу:
upcast или downcast нужно только сместить значение указателя на значение, известное во время компиляции
Таким образом, когда Dпередается в массив базового типа, приведение типа фактически вычисляет это смещение и указывает на то, что выглядит точно как действительное B2в памяти:
b2s[1]=&d;
за исключением того, что у этого есть vtable Dвместо B2, и поэтому все виртуальные вызовы работают прозрачно.
Теперь мы можем наконец вернуться к приведению типов и анализу нашего конкретного примера.
Из вывода stdout мы видим:
&d 0x7fffffffc930
b2s[1]0x7fffffffc940
Следовательно, неявное условие, static_castвыполненное там, правильно рассчитало смещение от полной Dструктуры данных в 0x7fffffffc930 до B2аналогичной структуры, которая находится в 0x7fffffffc940. Мы также заключаем, что то, что лежит между 0x7fffffffc930 и 0x7fffffffc940, скорее всего, является B1данными и vtable.
Затем в разделах, посвященных понижению, теперь легко понять, почему отказывают недействительные и почему:
static_cast<D*>(b2s[0]) 0x7fffffffc910: компилятор просто поднялся на 0x10 в байтах времени компиляции, чтобы попытаться перейти от a B2к содержащемуD
Но поскольку b2s[0]он не был D, теперь он указывает на неопределенную область памяти.
Сначала выполняется проверка NULL, и она возвращает NULL, если значение einput равно NULL.
В противном случае он устанавливает некоторые аргументы в RDX, RSI и RDI и вызовах __dynamic_cast.
У меня нет терпения, чтобы проанализировать это сейчас, но, как говорили другие, единственный способ для этого - __dynamic_castполучить доступ к некоторым дополнительным структурам данных RTTI в памяти, которые представляют иерархию классов.
Поэтому он должен начинаться с B2записи для этой таблицы, а затем обходить эту иерархию классов, пока не обнаружит, что vtable для типа Dприведен из b2s[0].
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940этот просто верит нам слепо: мы сказали, что есть Dадрес at b2s[1], а компилятор не выполняет вычисления смещения.
Но это неправильно, потому что D на самом деле в 0x7fffffffc930, то, что в 0x7fffffffc940 - это B2-подобная структура внутри D! Так что мусор становится доступным.
Мы можем подтвердить это из ужасной -O0сборки, которая просто перемещает значение:
Ответы:
static_cast
это первый каст, который вы должны попытаться использовать. Он выполняет такие вещи, как неявные преобразования между типами (например,int
tofloat
или указатель наvoid*
), и может также вызывать функции явного преобразования (или неявные). Во многих случаях явное указаниеstatic_cast
не требуется, но важно отметить, чтоT(something)
синтаксис эквивалентен(T)something
и его следует избегать (подробнее об этом позже). Однако AT(something, something_else)
безопасен и гарантированно вызывает конструктор.static_cast
может также приводиться через иерархии наследования. Это не нужно при приведении вверх (к базовому классу), но при приведении вниз его можно использовать до тех пор, пока оно не преобразуется черезvirtual
наследование. Однако он не выполняет проверку, и это неопределенное поведение -static_cast
приводить иерархию к типу, который на самом деле не является типом объекта.const_cast
может использоваться для удаления или добавленияconst
к переменной; никакой другой C ++ cast не способен удалить его (даже неreinterpret_cast
). Важно отметить, что изменение прежнегоconst
значения не определено, только если исходная переменная имеет значениеconst
; если вы используете его дляconst
удаления ссылки на то, что не было объявленоconst
, это безопасно. Это может быть полезно при перегрузке функций-членовconst
, например, на основе. Его также можно использовать для добавленияconst
к объекту, например, для вызова перегрузки функции-члена.const_cast
также работает аналогичноvolatile
, хотя это менее распространено.dynamic_cast
исключительно используется для обработки полиморфизма. Вы можете привести указатель или ссылку на любой полиморфный тип к любому другому типу класса (полиморфный тип имеет как минимум одну виртуальную функцию, объявленную или унаследованную). Вы можете использовать его не только для того, чтобы бросать вниз - вы можете кастовать вбок или даже на другую цепь.dynamic_cast
Будет искать нужный объект и вернуть его , если это возможно. Если он не может, он вернетсяnullptr
в случае указателя или выброситstd::bad_cast
в случае ссылки.dynamic_cast
имеет некоторые ограничения, хотя. Это не работает, если в иерархии наследования есть несколько объектов одного типа (так называемый «страшный бриллиант»), и вы не используетеvirtual
наследование. Она также может идти только через публичное наследование - это всегда будет не в путешествие черезprotected
илиprivate
наследования. Однако это редко является проблемой, поскольку такие формы наследования встречаются редко.reinterpret_cast
это самый опасный состав, и должен использоваться очень экономно. Он превращает один тип непосредственно в другой - например, приведение значения от одного указателя к другому или сохранение указателя вint
, или всякие другие неприятные вещи. В основном, единственная гарантия, которую вы получаете,reinterpret_cast
заключается в том, что обычно, если вы приведете результат обратно к исходному типу, вы получите точно такое же значение (но не в том случае, если промежуточный тип меньше исходного типа). Есть ряд преобразований, которыеreinterpret_cast
тоже не могут сделать. Он используется главным образом для особенно странных преобразований и битовых манипуляций, таких как превращение потока необработанных данных в реальные данные или хранение данных в младших битах указателя на выровненные данные.C-стиль литье и функция стиль бросок является слепки с использованием
(type)object
илиtype(object)
, соответственно, и функционально эквивалентны. Они определены как первое из следующего, которое успешно:const_cast
static_cast
(хотя игнорируя ограничения доступа)static_cast
(см. выше), затемconst_cast
reinterpret_cast
reinterpret_cast
, тогдаconst_cast
Поэтому в некоторых случаях он может использоваться в качестве замены для других приведений, но может быть чрезвычайно опасным из-за способности переходить в a
reinterpret_cast
, и последнее следует отдавать предпочтение, когда требуется явное приведение, если вы не уверены, чтоstatic_cast
это удастся илиreinterpret_cast
не удастся , Даже тогда рассмотрим более длинный и более явный вариант.Приведения типа C также игнорируют управление доступом при выполнении a
static_cast
, что означает, что они имеют возможность выполнять операции, которые не могут выполнять другие приведения. Хотя это в основном клудж, и, на мой взгляд, это просто еще одна причина избегать бросков в стиле C.источник
const
(даже неreinterpret_cast
)" ... правда? Как насчетreinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))
?reinterpret_cast
это часто является предпочтительным оружием при работе с набором непрозрачных типов данных APIИспользуется
dynamic_cast
для преобразования указателей / ссылок в иерархии наследования.Используйте
static_cast
для обычных преобразований типов.Используйте
reinterpret_cast
для низкоуровневой реинтерпретации битовых комбинаций. Используйте с особой осторожностью.Используйте
const_cast
для изгнанияconst/volatile
. Избегайте этого, если только вы не застряли с использованием некорректного API-интерфейса.источник
(Много теоретического и концептуального объяснения было дано выше)
Ниже приведены некоторые практические примеры, когда я использовал static_cast , dynamic_cast , const_cast , reinterpret_cast .
(Также ссылка на это, чтобы понять объяснение: http://www.cplusplus.com/doc/tutorial/typecasting/ )
static_cast:
dynamic_cast:
const_cast:
reinterpret_cast:
источник
static_cast<char*>(&val)
?static_cast
работает только между типами с определенными преобразованиями, видимым отношением по наследству или с / наvoid *
. Для всего остального есть другие забросы.reinterpret cast
любомуchar *
типу разрешено читать представление любого объекта - и это единственный из случаев, когда это ключевое слово полезно, а не безудержный генератор поведения реализации / неопределенности. Но это не считается «нормальным» преобразованием, поэтому не допускается (обычно) очень консервативнымstatic_cast
.Это может помочь, если вы знаете немного внутренностей ...
static_cast
static_cast
для них.A
вB
,static_cast
вызываетсяB
конструктор, передаваемыйA
как param. В качестве альтернативы,A
может иметь оператор преобразования (т.е.A::operator B()
). ЕслиB
такого конструктораA
нет или у вас нет оператора преобразования, вы получите ошибку времени компиляции.A*
вB*
всегда выполняется успешно, если A и B находятся в иерархии наследования (или void), в противном случае вы получите ошибку компиляции.A&
кB&
.dynamic_cast
(Base*)
чтобы(Derived*)
может потерпеть неудачу , если указатель не на самом деле производного типа.A*
чтобыB*
, если бросок недействителен , то dynamic_cast вернет nullptr.A&
чтобы ,B&
если литой недействителен , то dynamic_cast выбросит bad_cast исключения.const_cast
set<T>
который возвращает свои элементы только как const, чтобы убедиться, что вы не измените его ключ. Однако, если вы хотите изменить неключевые члены объекта, тогда все должно быть в порядке. Вы можете использовать const_cast для удаления константности.T& SomeClass::foo()
а такжеconst T& SomeClass::foo() const
. Чтобы избежать дублирования кода, вы можете применить const_cast для возврата значения одной функции из другой.reinterpret_cast
источник
If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.
Вы получаете UB, который может привести к segfault во время выполнения, если вам повезет. 2. Динамические броски могут также использоваться в перекрестном литье. 3. Const броски могут привести к UB в некоторых случаях. Использованиеmutable
может быть лучшим выбором для реализации логической константности.mutable
, кросс-кастингом и т. Д.Имеет ли это ответ на ваш вопрос?
Я никогда не использовал
reinterpret_cast
, и задаюсь вопросом, не пахнет ли случай, который нуждается в этом, плохим дизайном. В кодовой базе, над которой я работаю,dynamic_cast
много всего используется. Разница вstatic_cast
том, чтоdynamic_cast
проверка во время выполнения делает (что безопаснее) или не может (больше накладных расходов) то, что вы хотите (см. Msdn ).источник
reinterpret_cast
для извлечения фрагментов данных из массива. Например, если у меня естьchar*
большой буфер, заполненный упакованными двоичными данными, который мне нужно пройти и получить отдельные примитивы разных типов. Примерно так:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
reinterpret_cast
, там не очень много применений для этого.reinterpret_cast
по одной причине. Я видел необработанные данные объекта, хранящиеся в типе данных «blob» в базе данных, а затем, когда данные извлекаются из базы данных,reinterpret_cast
используется для преобразования этих необработанных данных в объект.В дополнение к другим ответам, приведенным выше, приведен неочевидный пример, когда
static_cast
этого недостаточно, так чтоreinterpret_cast
это необходимо. Предположим, есть функция, которая в выходном параметре возвращает указатели на объекты разных классов (которые не разделяют общий базовый класс). Реальный пример такой функцииCoCreateInstance()
(см. Последний параметр, который на самом делеvoid**
). Предположим, вы запрашиваете определенный класс объекта у этой функции, поэтому заранее знаете тип указателя (что вы часто делаете для COM-объектов). В этом случае вы не можете привести указатель к вашему указателюvoid**
с помощьюstatic_cast
: вам нужноreinterpret_cast<void**>(&yourPointer)
.В коде:
Однако
static_cast
работает для простых указателей (не указателей на указатели), поэтому приведенный выше код можно переписать, чтобы избежатьreinterpret_cast
(по цене дополнительной переменной) следующим образом:источник
&static_cast<void*>(pNetFwPolicy2)
вместоstatic_cast<void**>(&pNetFwPolicy2)
?В то время как другие ответы хорошо описывают все различия между приведениями C ++, я хотел бы добавить краткое замечание, почему вы не должны использовать приведения в стиле C
(Type) var
иType(var)
.Для начинающих в C ++ приведение в стиле C выглядит как операция надмножества над приведением в C ++ (static_cast <> (), dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()), и кто-то может предпочесть их перед приведением C ++ , Фактически, бросок в стиле C - это суперсет, и его можно писать короче.
Основная проблема приведений в стиле C заключается в том, что они скрывают реальные намерения разработчиков. Приведения в стиле C могут выполнять практически все типы приведения: от обычно безопасных приведения, выполняемых static_cast <> () и dynamic_cast <> (), к потенциально опасным приведениям, таким как const_cast <> (), где модификатор const можно удалить, поэтому переменные const может быть изменен и reinterpret_cast <> (), который может даже интерпретировать целочисленные значения для указателей.
Вот образец.
Основная причина, по которой в язык были добавлены приведения C ++, состояла в том, чтобы позволить разработчику уточнить свои намерения - почему он собирается выполнять это приведение. Используя приведение в стиле C, которое является абсолютно допустимым в C ++, вы делаете свой код менее читаемым и более подверженным ошибкам, особенно для других разработчиков, которые не создавали ваш код. Поэтому, чтобы сделать ваш код более читабельным и явным, вы всегда должны отдавать предпочтение приведениям C ++ по сравнению с C-стилями.
Вот небольшая цитата из книги Бьярна Страуструпа (автора C ++) «Язык программирования C ++, 4-е издание» - стр. 302.
источник
Чтобы понять, давайте рассмотрим ниже фрагмент кода:
Только строка (4) компилируется без ошибок. Только reinterpret_cast может использоваться для преобразования указателя на объект в указатель на любой не связанный тип объекта.
Следует отметить следующее: dynamic_cast не будет работать во время выполнения, однако на большинстве компиляторов он также не будет компилироваться, поскольку в структуре преобразуемого указателя нет виртуальных функций, то есть dynamic_cast будет работать только с полиморфными указателями классов. ,
Когда использовать C ++ cast :
источник
static_cast
поdynamic_cast
сравнению сreinterpret_cast
внутренним видом на downcast / upcastВ этом ответе я хочу сравнить эти три механизма на конкретном примере upcast / downcast и проанализировать, что происходит с базовыми указателями / памятью / сборкой, чтобы дать конкретное понимание того, как они сравниваются.
Я считаю, что это даст хорошую интуицию о том, как эти броски отличаются:
static_cast
: делает одно смещение адреса во время выполнения (низкое влияние времени выполнения) и не проверяет безопасность, что даунскейт корректен.dyanamic_cast
: делает то же самое смещение адреса во время выполнения, какstatic_cast
, но также и дорогостоящую проверку безопасности, что обратное преобразование корректно, используя RTTI.Эта проверка безопасности позволяет вам запрашивать, имеет ли указатель базового класса заданный тип во время выполнения, проверяя возвращение
nullptr
которого указывает на недопустимое снижение.Поэтому, если ваш код не может проверить это
nullptr
и выполнить допустимое действие без прерывания, вы должны просто использоватьstatic_cast
вместо динамического приведения.Если прерывание - это единственное действие, которое может выполнить ваш код, возможно, вам нужно только включить
dynamic_cast
in in debug builds (-NDEBUG
) и использоватьstatic_cast
иначе, например, как здесь , чтобы не замедлять ваши быстрые запуски.reinterpret_cast
: ничего не делает во время выполнения, даже смещение адреса. Указатель должен точно указывать на правильный тип, даже базовый класс не работает. Обычно вы этого не хотите, если не задействованы необработанные потоки байтов.Рассмотрим следующий пример кода:
main.cpp
Скомпилируйте, запустите и разберите с помощью:
где
setarch
это используется , чтобы отключить ASLR , чтобы облегчить сравнение прогонов.Возможный вывод:
Теперь, как уже упоминалось по адресу: https://en.wikipedia.org/wiki/Virtual_method_table , для эффективной поддержки вызовов виртуальных методов структура данных в памяти
D
должна выглядеть примерно так:Ключ в том, что структура данных памяти
D
содержит внутри него структуру памяти совместима сB1
и изB2
внутренне.Поэтому мы приходим к критическому выводу:
Таким образом, когда
D
передается в массив базового типа, приведение типа фактически вычисляет это смещение и указывает на то, что выглядит точно как действительноеB2
в памяти:за исключением того, что у этого есть vtable
D
вместоB2
, и поэтому все виртуальные вызовы работают прозрачно.Теперь мы можем наконец вернуться к приведению типов и анализу нашего конкретного примера.
Из вывода stdout мы видим:
Следовательно, неявное условие,
static_cast
выполненное там, правильно рассчитало смещение от полнойD
структуры данных в 0x7fffffffc930 доB2
аналогичной структуры, которая находится в 0x7fffffffc940. Мы также заключаем, что то, что лежит между 0x7fffffffc930 и 0x7fffffffc940, скорее всего, являетсяB1
данными и vtable.Затем в разделах, посвященных понижению, теперь легко понять, почему отказывают недействительные и почему:
static_cast<D*>(b2s[0]) 0x7fffffffc910
: компилятор просто поднялся на 0x10 в байтах времени компиляции, чтобы попытаться перейти от aB2
к содержащемуD
Но поскольку
b2s[0]
он не былD
, теперь он указывает на неопределенную область памяти.Разборка это:
Итак, мы видим, что GCC делает:
D
чего не существует.dynamic_cast<D*>(b2s[0]) 0
: C ++ на самом деле обнаружил, что приведение было недействительным и вернулсяnullptr
!Это невозможно сделать во время компиляции, и мы подтвердим это после разборки:
Сначала выполняется проверка NULL, и она возвращает NULL, если значение einput равно NULL.
В противном случае он устанавливает некоторые аргументы в RDX, RSI и RDI и вызовах
__dynamic_cast
.У меня нет терпения, чтобы проанализировать это сейчас, но, как говорили другие, единственный способ для этого -
__dynamic_cast
получить доступ к некоторым дополнительным структурам данных RTTI в памяти, которые представляют иерархию классов.Поэтому он должен начинаться с
B2
записи для этой таблицы, а затем обходить эту иерархию классов, пока не обнаружит, что vtable для типаD
приведен изb2s[0]
.Вот почему реинтерпретация броска потенциально дорогая! Вот пример, где один патч, конвертирующий
dynamic_cast
astatic_cast
в сложный проект, сократил время выполнения на 33%! ,reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
этот просто верит нам слепо: мы сказали, что естьD
адрес atb2s[1]
, а компилятор не выполняет вычисления смещения.Но это неправильно, потому что D на самом деле в 0x7fffffffc930, то, что в 0x7fffffffc940 - это B2-подобная структура внутри D! Так что мусор становится доступным.
Мы можем подтвердить это из ужасной
-O0
сборки, которая просто перемещает значение:Смежные вопросы:
Протестировано на Ubuntu 18.04 amd64, GCC 7.4.0.
источник