Когда следует использовать static_cast, dynamic_cast, const_cast и reinterpret_cast?

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

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • C-стиль (type)value
  • Литье в стиле функции type(value)

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

Ответы

Ответ 1

static_cast - первый каст, который вы должны попытаться использовать. Он выполняет такие вещи, как неявные преобразования между типами (например, int для float или указатель на void*), и может также вызывать явные функции преобразования (или неявные). Во многих случаях явное указание static_cast необязательно, но важно отметить, T(something) синтаксис T(something) эквивалентен (T)something и его следует избегать (подробнее об этом позже). Однако T(something, something_else) безопасно и гарантированно вызывает конструктор.

static_cast также может static_cast через иерархии наследования. Это не нужно при приведении вверх (к базовому классу), но при приведении вниз его можно использовать до тех пор, пока оно не преобразуется посредством virtual наследования. Однако он не выполняет проверку, и поведение static_cast вниз по иерархии до типа, который на самом деле не является типом объекта, не определено.


const_cast может использоваться для удаления или добавления const в переменную; никакое другое приведение C++ не способно удалить его (даже 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 не удастся. Даже тогда рассмотрим более длинный и более явный вариант.

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

Ответ 2

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

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

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

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

Ответ 3

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

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

Ответ 4

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

static_cast

  • Компилятор C++ уже знает, как преобразовывать типы масштабирования, такие как float, в int. Используйте static_cast для них.
  • Когда вы просите компилятор преобразовать тип A в B, static_cast вызывает конструктор B передавая A качестве параметра. В качестве альтернативы, A может иметь оператор преобразования (т.е. A::operator B()). Если B не имеет такого конструктора или A не имеет оператора преобразования, вы получите ошибку времени компиляции.
  • Преобразование из A* в B* всегда успешно, если A и B находятся в иерархии наследования (или void), в противном случае вы получите ошибку компиляции.
  • Поправка: если вы приведете базовый указатель к производному указателю, но если фактический объект не является действительно производным типом, вы не получите ошибку. Вы получаете плохой указатель и, скорее всего, segfault во время выполнения. То же самое касается A& B&.
  • Попался: приведение от Derived к Base или наоборот создаст новую копию! Для людей, пришедших из С#/Java, это может быть огромным сюрпризом, поскольку в результате получается в основном отрубленный объект, созданный из Derived.

dynamic_cast

  • dynamic_cast использует информацию о типе среды выполнения, чтобы выяснить, является ли приведение действительным. Например, от (Base*) до (Derived*) может произойти сбой, если указатель фактически не является производным типом.
  • Это означает, что dynamic_cast очень дорогой по сравнению со static_cast!
  • Для A* к B*, если приведение неверно, то dynamic_cast вернет nullptr.
  • Для A& to 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.
  • Для этого состава нет накладных расходов времени выполнения.

Ответ 5

Помогает ли это ответить на ваш вопрос?

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

Ответ 6

В дополнение к остальным ответам до сих пор здесь неочевидный пример, где 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);

Ответ 7

В то время как другие ответы хорошо описывали все различия между C++ кастами, я хотел бы добавить короткую заметку, почему вы не должны использовать C-style cast (Type) var и Type(var).

Для новичков C++ C-стиль выглядит как операция надмножества над C++ casts (static_cast <>(), dynamic_cast <>(), const_cast <>(), reinterpret_cast <>()), и кто-то может предпочесть их над C++. Фактически, C-стиль - это надмножество и короче, чтобы писать.

Основная проблема приведения в стиле C заключается в том, что они скрывают реальное намерение разработчика при создании. Стили C-стиля могут выполнять практически все типы кастинга из обычно безопасных отбросов, выполняемых static_cast <>() и dynamic_cast <>(), в потенциально опасные роли, такие как const_cast <>(), где модификатор 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-style casts, которые отлично подходят в C++, вы делаете свой код менее удобочитаемым и больше подвержены ошибкам, особенно для других разработчиков, которые не создали ваш код. Поэтому, чтобы сделать ваш код более читабельным и явным, вы всегда должны отдавать предпочтение C++ за кастинг над стилями C-стиля.

Вот короткая цитата из Bjarne Stroustrup (автора C++) книги C++ Язык программирования 4-го издания - стр. 302.

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

Ответ 8

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

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++ приведение:

  • Используйте static_cast в качестве эквивалента приведения типа C, который выполняет преобразование значений, или когда нам нужно явно преобразовать указатель из класса в его суперкласс.
  • Используйте const_cast для удаления квалификатора const.
  • Используйте reinterpret_cast для небезопасных преобразований типов указателей в целочисленные и из других типов указателей и обратно. Используйте это, только если мы знаем, что делаем, и понимаем проблемы псевдонимов.