Когда следует использовать static_cast, dynamic_cast, const_cast и reinterpret_cast?
Как правильно использовать:
static_cast
dynamic_cast
const_cast
reinterpret_cast
- С-стиль
(type)value
- Функциональный стиль
type(value)
Как решить, что использовать в каких конкретных случаях?
12 ответов
static_cast
это первый каст, который вы должны попытаться использовать. Он делает такие вещи, как неявные преобразования между типами (например, int
в float
или указатель на void*
), и он также может вызывать явные функции преобразования (или неявные). Во многих случаях явно static_cast
не обязательно, но важно отметить, что T(something)
синтаксис эквивалентен (T)something
и следует избегать (подробнее об этом позже). 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 и в стиле function - это приведение с использованием (type)object
или же type(object)
соответственно и являются функционально эквивалентными. Они определены как первое из следующего, которое успешно:
const_cast
static_cast
(хотя игнорируя ограничения доступа)static_cast
(см. выше), затемconst_cast
reinterpret_cast
reinterpret_cast
, затемconst_cast
Поэтому в некоторых случаях он может использоваться в качестве замены для других приведений, но может быть чрезвычайно опасным из-за способности превращаться в reinterpret_cast
и последнее должно быть предпочтительным, когда требуется явное приведение, если вы не уверены static_cast
удастся или reinterpret_cast
не удастся. Даже тогда рассмотрим более длинный и более явный вариант.
Приведения типа C также игнорируют контроль доступа при выполнении static_cast
Это означает, что у них есть возможность выполнить операцию, которую не может выполнить ни один другой актер. Хотя это в основном клудж, и, на мой взгляд, это просто еще одна причина избегать бросков в стиле C.
Использование 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:
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);
.....
}
dynamic_cast:
void DebugLog::OnMessage(Message *msg)
{
static DebugMsgData *debug;
static XYZMsgData *xyz;
if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
// debug message
}
else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
// xyz message
}
else/* if( ... )*/{
// ...
}
}
const_cast:
// *Passwd declared as a const
const unsigned char *Passwd
// on some situation it require to remove its constness
const_cast<unsigned char*>(Passwd)
reinterpret_cast:
typedef unsigned short uint16;
// Read Bytes returns that 2 bytes got read.
bool ByteBuffer::ReadUInt16(uint16& val) {
return ReadBytes(reinterpret_cast<char*>(&val), 2);
}
Это может помочь, если вы знаете немного внутренних...
static_cast
- Компилятор C++ уже знает, как преобразовывать типы масштабирования, такие как float, в int. использование
static_cast
для них. - Когда вы просите компилятор конвертировать из типа
A
вB
,static_cast
звонкиB
Проходящий конструкторA
как парам. С другой стороны,A
может иметь оператор преобразования (т.е.A::operator B()
). ЕслиB
не имеет такого конструктора, илиA
не имеет оператора преобразования, тогда вы получите ошибку времени компиляции. - В ролях от
A*
вB*
всегда успешен, если A и B находятся в иерархии наследования (или недействительны), иначе вы получите ошибку компиляции. - Поправка: если вы приведете базовый указатель к производному указателю, но если фактический объект не является действительно производным типом, вы не получите ошибку. Вы получаете плохой указатель и, скорее всего, 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_cast может работать в обоих направлениях.
- Одним из примеров, где это удобно, является итерация по некоторому контейнеру, например
set<T>
который только возвращает свои элементы как const, чтобы убедиться, что вы не измените его ключ. Однако, если ваше намерение состоит в том, чтобы изменить неключевые члены объекта, тогда все должно быть в порядке. Вы можете использовать const_cast для удаления константности. - Другой пример, когда вы хотите реализовать
T& foo()
так же какconst T& foo()
, Чтобы избежать дублирования кода, вы можете применить const_cast для возврата значения одной функции из другой.
reinterpret_cast
- Это в основном говорит о том, что возьмите эти байты в этой ячейке памяти и воспринимайте это как заданный объект.
- Например, вы можете загрузить 4 байта с плавающей точкой до 4 байтов с целым числом, чтобы увидеть, как выглядят биты в плавающей точке.
- Очевидно, что если данные не соответствуют типу, вы можете получить segfault.
- Для этого состава нет накладных расходов времени выполнения.
static_cast
против dynamic_cast
против reinterpret_cast
вид изнутри на понижающий / повышающий
В этом ответе я хочу сравнить эти три механизма на конкретном примере восходящего / нисходящего преобразования и проанализировать, что происходит с базовыми указателями / памятью / сборкой, чтобы дать конкретное понимание того, как они сравниваются.
Я считаю, что это даст хорошее представление о различиях между этими приведениями:
static_cast
: выполняет одно адресное смещение во время выполнения (низкое влияние на время выполнения) и не проверяет безопасность правильности понижающего преобразования.dyanamic_cast
: такое же смещение адреса во время выполнения, какstatic_cast
, но также и дорогостоящая проверка безопасности правильности понижающего преобразования с использованием RTTI.Эта проверка безопасности позволяет вам запросить, имеет ли указатель базового класса данный тип во время выполнения, проверив возврат
nullptr
что указывает на недопустимое понижающее преобразование.Поэтому, если ваш код не может проверить это
nullptr
и предпринять допустимое действие без прерывания, вы должны просто использоватьstatic_cast
вместо динамического приведения.Если прерывание - единственное действие, которое может предпринять ваш код, возможно, вы хотите только включить
dynamic_cast
в отладочных сборках (-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() {}
virtual int f1() { return 1; }
int int_in_b1;
};
struct B2 {
B2(int int_in_b2) : int_in_b2(int_in_b2) {}
virtual ~B2() {}
virtual int f2() { return 2; }
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() { return 3; }
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;
}
Скомпилируйте, запустите и дизассемблируйте с помощью:
g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out
где setarch
будет использоваться для отключения ASLR, чтобы облегчить сравнение прогонов.
Возможный выход:
&d 0x7fffffffc930
b2s[0] 0x7fffffffc920
b2s[1] 0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0]) 0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d 1
static_cast<D*>(b2s[1]) 0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d 3
dynamic_cast<D*>(b2s[0]) 0
dynamic_cast<D*>(b2s[1]) 0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767
Теперь, как упоминалось по адресу: 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
внутренне.
Таким образом, мы приходим к критическому выводу:
при повышении или понижении необходимо только сдвинуть значение указателя на значение, известное во время компиляции
Таким образом, когда 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 байтов во время компиляции, чтобы попытаться перейти сB2
к содержащемуD
Но потому что
b2s[0]
не былD
, теперь он указывает на неопределенную область памяти.Разборка:
49 dp = static_cast<D*>(b2s[0]); 0x0000000000000fc8 <+414>: 48 8b 45 d0 mov -0x30(%rbp),%rax 0x0000000000000fcc <+418>: 48 85 c0 test %rax,%rax 0x0000000000000fcf <+421>: 74 0a je 0xfdb <main()+433> 0x0000000000000fd1 <+423>: 48 8b 45 d0 mov -0x30(%rbp),%rax 0x0000000000000fd5 <+427>: 48 83 e8 10 sub $0x10,%rax 0x0000000000000fd9 <+431>: eb 05 jmp 0xfe0 <main()+438> 0x0000000000000fdb <+433>: b8 00 00 00 00 mov $0x0,%eax 0x0000000000000fe0 <+438>: 48 89 45 98 mov %rax,-0x68(%rbp)
Итак, мы видим, что GCC делает:
- проверьте, является ли указатель NULL, и если да, верните NULL
- в противном случае вычтите из него 0x10, чтобы получить
D
которого не существует
dynamic_cast<D*>(b2s[0]) 0
: C++ действительно обнаружил, что приведение неверно, и вернулnullptr
!Это невозможно сделать во время компиляции, и мы подтвердим это при разборке:
59 dp = dynamic_cast<D*>(b2s[0]); 0x00000000000010ec <+706>: 48 8b 45 d0 mov -0x30(%rbp),%rax 0x00000000000010f0 <+710>: 48 85 c0 test %rax,%rax 0x00000000000010f3 <+713>: 74 1d je 0x1112 <main()+744> 0x00000000000010f5 <+715>: b9 10 00 00 00 mov $0x10,%ecx 0x00000000000010fa <+720>: 48 8d 15 f7 0b 20 00 lea 0x200bf7(%rip),%rdx # 0x201cf8 <_ZTI1D> 0x0000000000001101 <+727>: 48 8d 35 28 0c 20 00 lea 0x200c28(%rip),%rsi # 0x201d30 <_ZTI2B2> 0x0000000000001108 <+734>: 48 89 c7 mov %rax,%rdi 0x000000000000110b <+737>: e8 c0 fb ff ff callq 0xcd0 <__dynamic_cast@plt> 0x0000000000001110 <+742>: eb 05 jmp 0x1117 <main()+749> 0x0000000000001112 <+744>: b8 00 00 00 00 mov $0x0,%eax 0x0000000000001117 <+749>: 48 89 45 98 mov %rax,-0x68(%rbp)
Сначала выполняется проверка на NULL, и она возвращает NULL, если вход равен NULL.
В противном случае он устанавливает некоторые аргументы в RDX, RSI и RDI и вызывает
__dynamic_cast
.У меня нет терпения анализировать это дальше, но, как говорили другие, единственный способ, чтобы это сработало, - это
__dynamic_cast
для доступа к некоторым дополнительным структурам данных RTTI в памяти, которые представляют иерархию классов.Поэтому он должен начинаться с
B2
запись для этой таблицы, затем пройдитесь по этой иерархии классов, пока не обнаружите, что vtable дляD
приведение типа изb2s[0]
.Вот почему переосмысление гипса потенциально дорого! Вот пример, когда патч с одним лайнером преобразует
dynamic_cast
кstatic_cast
в сложном проекте время выполнения сокращено на 33%!.reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940
этот просто слепо нам верит: мы сказали, что естьD
по адресуb2s[1]
, и компилятор не выполняет вычислений смещения.Но это неправильно, потому что D на самом деле находится по адресу 0x7fffffffc930, то, что находится по адресу 0x7fffffffc940, является структурой типа B2 внутри D! Таким образом, есть доступ к мусору.
Мы можем подтвердить это ужасающим
-O0
сборка, которая просто перемещает значение:70 dp = reinterpret_cast<D*>(b2s[1]); 0x00000000000011fa <+976>: 48 8b 45 d8 mov -0x28(%rbp),%rax 0x00000000000011fe <+980>: 48 89 45 98 mov %rax,-0x68(%rbp)
Связанные вопросы:
- Когда следует использовать static_cast, dynamic_cast, const_cast и reinterpret_cast?
- Как реализовано dynamic_cast
- Понижение с использованием static_cast в C++
Протестировано на Ubuntu 18.04 amd64, GCC 7.4.0.
Это отвечает на ваш вопрос?
Я никогда не пользовалась reinterpret_cast
и задаюсь вопросом, не является ли столкновение с делом, которое в этом нуждается, запахом плохого дизайна. В кодовой базе, над которой я работаю dynamic_cast
используется много Разница с static_cast
это dynamic_cast
выполняет проверку во время выполнения, которая может (безопаснее) или не может (больше накладных расходов) быть тем, что вы хотите (см. msdn).
В дополнение к другим ответам пока приведен неочевидный пример 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 error
reinterpret_cast<void**>(&pNetFwPolicy2) );
Тем не мение, static_cast
работает для простых указателей (не указателей на указатели), поэтому приведенный выше код можно переписать, чтобы избежать reinterpret_cast
(по цене дополнительной переменной) следующим образом:
#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
&tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);
В то время как другие ответы хорошо описывают все различия между приведениями 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 гораздо опаснее, чем именованные операторы преобразования, потому что обозначения труднее обнаружить в большой программе, а вид преобразования, предназначенный программистом, не является явным.
Чтобы понять, давайте рассмотрим ниже фрагмент кода:
struct Foo{};
struct Bar{};
int main(int argc, char** argv)
{
Foo* f = new Foo;
Bar* b1 = f; // (1)
Bar* b2 = static_cast<Bar*>(f); // (2)
Bar* b3 = dynamic_cast<Bar*>(f); // (3)
Bar* b4 = reinterpret_cast<Bar*>(f); // (4)
Bar* b5 = const_cast<Bar*>(f); // (5)
return 0;
}
Только строка (4) компилируется без ошибок. Только reinterpret_cast может использоваться для преобразования указателя на объект в указатель на любой не связанный тип объекта.
Следует отметить следующее: dynamic_cast не будет работать во время выполнения, однако на большинстве компиляторов он также не будет компилироваться, поскольку в структуре преобразуемого указателя нет виртуальных функций, то есть dynamic_cast будет работать только с полиморфными указателями классов.,
Когда использовать C++ cast:
- Используйте static_cast в качестве эквивалента приведения типа C, который выполняет преобразование значений, или когда нам нужно явно преобразовать указатель из класса в его суперкласс.
- Используйте const_cast для удаления квалификатора const.
- Используйте reinterpret_cast для небезопасных преобразований типов указателей в целочисленные и из других типов указателей и обратно. Используйте это, только если мы знаем, что делаем, и понимаем проблемы псевдонимов.
Я думаю, нам нужно более понятное для новичков объяснение, и после того, как я сам изучил эту тему, я думаю, что лучшее, что я нашел, находится здесь: https://www.tutorialspoint.com/When-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-use-in-Cplusplus [1]
Когда следует использовать , и в C++?
может использоваться для удаления или добавления константы к переменной. Это может быть полезно, если необходимо добавить/удалить константность переменной.
Это используется для преобразования нормального/обычного типа. Это также приведение типов, отвечающее за неявное приведение типов, и его также можно вызывать явно. Вы должны использовать его в таких случаях, как преобразование float в int, char в int и т. д.
Это приведение используется для обработки полиморфизма. Вам нужно использовать его только при приведении к производному классу. Это предназначено исключительно для использования при наследовании при приведении базового класса к производному классу.
- Мои собственные добавленные слова: он позволяет безопасно преобразовать ptr в базовый класс (который был создан путем использования адреса производного класса, наследуемого от этого базового класса) в ptr в производный класс, гарантируя, что ptr действительно указывает к полному, законченному объекту производного класса (и возвращая
nullptr
если не). В разделе «» есть отличный пример кода: . (И я обсуждаю этот код в своем ответе здесь ).
Это самое сложное в использовании. Он используется для переосмысления битовых комбинаций и имеет чрезвычайно низкий уровень. Он используется в основном для таких вещей, как преобразование битового потока необработанных данных в фактические данные или сохранение данных в младших битах выровненного указателя.
Затем, для более глубокого погружения, прочитайте следующее:
Ответ Quora от @Brian Bi: https://qr.ae/prz8xL — очень хороший, вдумчивый, хорошо написанный и подробный ответ. Вот краткое изложение с конца [порядок изменен так, как указано выше]:
-
const_cast
меняет только cv-квалификацию; все остальные приведения не могут отбросить константность. -
static_cast
выполняет неявные преобразования, обратные неявные стандартные преобразования и (возможно, небезопасные) преобразования базы в производные. -
dynamic_cast
выполняет только иерархию классов вверх и вниз, всегда проверяя правильность запрошенного преобразования. -
reinterpret_cast
преобразует один указатель в другой без изменения адреса или преобразует указатели в их числовые (целые) значения.
-
[Легко читать; написано для всех; очень информативно] https://cplusplus.com/doc/tutorial/typecasting/https://cplusplus.com/doc/tutorial/typecasting/ — эта статья также содержит примеры кода для каждого типа приведения!
[педантичный, языковой юрист, трудно читается, но более тщательный] CppReference wiki:
- https://en.cppreference.com/w/cpp/language/const_cast
- https://en.cppreference.com/w/cpp/language/static_cast
- https://en.cppreference.com/w/cpp/language/dynamic_cast
- https://en.cppreference.com/w/cpp/language/reinterpret_cast
-
- https://en.cppreference.com/w/cpp/language/explicit_cast
- https://en.cppreference.com/w/cpp/language/implicit_cast
1 Примечание. TutorialsPoint известен плагиатом и отсутствием цитирования источников . Я думаю, что они на самом деле взяли формулировку из Основной ответ вики-сообщества здесьосновного ответа вики сообщества здесь, не цитируя ее. Тем не менее, мне нравится чрезвычайная краткость и простота их статьи, благодаря которой ее легко понять новичку или быстро просмотреть тем, кто нуждается в повышении квалификации во время или непосредственно перед собеседованием или тестом.
Посмотрим разницу между и на примере:
#include <iostream>
using namespace std;
class A
{
int a;
};
class B
{
int b;
};
class C : public A, public B
{
int c;
};
int main()
{
{
B b;
cout << &b << endl;
cout << static_cast<C *>(&b) << endl; // 1
cout << reinterpret_cast<C *>(&b) << endl; // 2
}
cout << endl;
{
C c;
cout << &c << endl;
cout << static_cast<B *>(&c) << endl; // 3
cout << reinterpret_cast<B *>(&c) << endl; // 4
}
cout << endl;
{
A a;
cout << &a << endl;
cout << static_cast<C *>(&a) << endl;
cout << reinterpret_cast<C *>(&a) << endl;
}
cout << endl;
{
C c;
cout << &c << endl;
cout << static_cast<A *>(&c) << endl;
cout << reinterpret_cast<A *>(&c) << endl;
}
return 0;
}
Производит вывод:
0x7ffcede34f0c
0x7ffcede34f08 // 1
0x7ffcede34f0c // 2
0x7ffcede34f0c
0x7ffcede34f10 // 3
0x7ffcede34f0c // 4
0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c
0x7ffcede34f0c
Обратите внимание, что вывод1
и2
бывают разные, как и3
и4
. Почему это? Один из них, а другой - к одному и тому же типу ввода в обоих случаях.
Ситуацию можно представить на следующем рисунке:
содержит a, но начальный адрес не совпадает с .static_cast
правильно вычисляет адрес внутри . Однакоreinterpret_cast
возвращает тот же адрес, который мы даем в качестве входных данных, что неверно в данном случае:B
по этому адресу.
Однако оба приведения возвращают одинаковые результаты при преобразовании междуA
иC
указатели, потому что они начинаются в одном и том же месте, что, кстати, не гарантируется стандартом.
Хорошая особенность , не упомянутая в других ответах, заключается в том, что она позволяет нам создать своего рода указатель для типов функций. Обычно для типов объектов используется для извлечения исходного типа указателя, хранящегося в :
int i = 13;
void *p = &i;
auto *pi = static_cast<int*>(p);
Для функций мы должны использовать дважды:
#include<iostream>
using any_fcn_ptr_t = void(*)();
void print(int i)
{
std::cout << i <<std::endl;
}
int main()
{
//Create type-erased pointer to function:
auto any_ptr = reinterpret_cast<any_fcn_ptr_t>(&print);
//Retrieve the original pointer:
auto ptr = reinterpret_cast< void(*)(int) >(any_ptr);
ptr(7);
}
С помощью мы можем даже получить аналогичный указатель типа void* для указателей на функции-члены.
как с обычным
void*
а также
static_cast
, C++ гарантирует, что
ptr
указывает на
print
функция (пока мы передаем правильный тип в
reinterpret_cast
).