Когда следует использовать static_cast, dynamic_cast, const_cast и reinterpret_cast? - PullRequest
2274 голосов
/ 01 декабря 2008

Как правильно использовать:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • В стиле C (type)value
  • Функциональный стиль исполнения type(value)

Как решить, что и в каких случаях использовать?

Ответы [ 8 ]

2378 голосов
/ 01 декабря 2008

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 и приведение в функциональном стиле являются приведениями с использованием (type)object или type(object) соответственно и функционально эквивалентны. Они определены как первое из следующего, которое завершается успешно:

  • const_cast
  • static_cast (без учета ограничений доступа)
  • static_cast (см. Выше), затем const_cast
  • reinterpret_cast
  • reinterpret_cast, затем const_cast

Поэтому в некоторых случаях он может использоваться как замена для других приведений, но может быть чрезвычайно опасным из-за способности переходить в reinterpret_cast, и последнее должно быть предпочтительным, когда требуется явное приведение, если вы не обязательно static_cast удастся или reinterpret_cast не удастся. Даже в этом случае рассмотрим более длинный и более явный вариант.

* 109Приведения типа 8 * C также игнорируют управление доступом при выполнении static_cast, что означает, что они имеют возможность выполнять операции, которые не могут выполнять другие приведения. Хотя это в основном клудж, и, на мой взгляд, это просто еще одна причина избегать бросков в стиле C.
306 голосов
/ 01 декабря 2008

Используйте dynamic_cast для преобразования указателей / ссылок в иерархии наследования.

Используйте static_cast для обычных преобразований типов.

Используйте reinterpret_cast для низкоуровневой реинтерпретации битовых комбинаций. Используйте с особой осторожностью.

Используйте const_cast для отбрасывания const/volatile. Избегайте этого, если только вы не застряли с использованием некорректного API-интерфейса.

179 голосов
/ 21 января 2014

(много теоретических и концептуальных объяснений было дано выше)

Ниже приведены некоторые практические примеры , когда я использовал 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);
}
73 голосов
/ 11 декабря 2016

Это может помочь, если вы немного знакомы с внутренностями ...

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 или наоборот создает new copy! Для людей, пришедших из 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.
  • Для этого броска нет времени выполнения.
12 голосов
/ 31 мая 2015

В дополнение к другим ответам на данный момент приведен неочевидный пример, где 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);
12 голосов
/ 01 декабря 2008

это ответит на ваш вопрос?

Я никогда не использовал reinterpret_cast, и задаюсь вопросом, не пахнет ли случай, который нуждается в нем, запахом плохого дизайна. В кодовой базе я работаю на dynamic_cast используется много. Разница с static_cast заключается в том, что dynamic_cast выполняет проверку во время выполнения, которая может (безопаснее) или не может (больше накладных расходов) быть тем, что вы хотите (см. msdn ).

6 голосов
/ 22 августа 2018

В то время как другие ответы хорошо описывают все различия между приведениями в 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-style.

Вот небольшая цитата из книги Бьярна Страуструпа (автора C ++) «Язык программирования C ++, 4-е издание» - стр. 302.

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

0 голосов
/ 21 декабря 2018

Чтобы понять, давайте рассмотрим следующий фрагмент кода:

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 для небезопасных преобразований типов указателей в целочисленные и другие типы указателей и из них. Используйте это, только если мы знаем, что делаем, и понимаем проблемы псевдонимов.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...