Когда следует использовать статическое литье, динамическое литье, const cast и reinterpret cast?



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




  • static_cast

  • dynamic_cast

  • const_cast

  • reinterpret_cast

  • C-style cast (type)value

  • функция-стиль cast type(value)


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

639   7  

7 ответов:

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

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


const_cast можно использовать для удаления или добавления const to переменная; никакое другое приведение 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-style cast и

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

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

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

использовать const_cast для отбрасывания const/volatile. Избегайте этого, если вы не застряли с помощью const-неправильного 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 to B,static_cast звонки Bконструктор проходит A парам. Если B нет такого конструктора, то вы получите ошибку времени компиляции.
  • Cast from A* до B* всегда успешно, если A и B находятся в иерархии наследования (или void) в противном случае вы получаете ошибку компиляции.
  • понял: если вы приведете базовый указатель к производному указателю, но если фактический объект на самом деле не является производным типом, то вы не получаю ошибку. Вы получаете плохой указатель и segfault во время выполнения. То же самое касается A& до B&.
  • понял: приведение от производного к основанию или наоборот создает новая понял! Для людей, пришедших из C# / Java, это может быть огромным сюрпризом.

операцию 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_cast может работать в обоих направлениях.
  • один пример, где это удобно, - это итерация через какой-то контейнер, например set<T> что только возвращает его элементы как const, чтобы убедиться, что вы не измените своего ключа. Однако, если ваше намерение состоит в том, чтобы изменить неключевые члены объекта, то это должно быть в порядке. Вы можете использовать const_cast для удаления constness.
  • другой пример, когда вы хотите реализовать T& foo() а также const T& foo(). Чтобы избежать дублирования кода, можно применить const_cast для возврата значения одной функции из другой.

оператора reinterpret_cast

  • это в основном говорит, что возьмите эти байты в этом месте памяти и подумайте об этом как о данном объекте.
  • например, вы можете загрузить 4 байта float в 4 байта int, чтобы увидеть, как выглядят биты в float.
  • очевидно, что если данные неверны для типа, вы можете получить segfault.
  • для этого приведения нет накладных расходов во время выполнения.

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

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

в дополнение к другим ответам до сих пор, вот неочевидный пример, где static_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++ casts, я хотел бы добавить короткую заметку, почему вы не должны использовать C-style casts (Type) var и Type(var).

для новичков C++ приведения в стиле C выглядят как операция над набором над приведениями C++ (static_cast (), dynamic_cast (), const_cast (), reinterpret_cast ()) и кто-то может предпочесть их над приведениями C++. На самом деле c-стиль cast является надмножеством и короче писать.

главная проблема 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 намного опаснее, чем именованные операторы преобразования потому что обозначение труднее обнаружить в большой программе, и вид преобразования, предназначенный программистом, не является явным.

Comments

    Ничего не найдено.