8

Почему стоит использовать static_cast<T>(x) вместо (T)x?

7

Я слышал, что функцию static_cast следует предпочитать кастингу в стиле C или простому вызову функции для приведения типов. Это правда? Почему?

5 ответ(ов)

7

Основная причина заключается в том, что классические приведения типов в C не делают различия между тем, что мы называем static_cast<>(), reinterpret_cast<>(), const_cast<>() и dynamic_cast<>(). Эти четыре операции совершенно разные.

static_cast<>() обычно безопасен. В языке существует допустимое преобразование, или имеется соответствующий конструктор, который это позволяет. Единственный случай, когда это может быть рискованно — это если вы приводите к производному классу; нужно убедиться, что объект действительно является тем потомком, который вы утверждаете, используя методы, не зависящие от языка (например, флаг в объекте). dynamic_cast<>() безопасен, если результат проверяется (указатель) или учитывается возможное исключение (ссылка).

С другой стороны, reinterpret_cast<>() (или const_cast<>()) всегда небезопасны. Вы сообщаете компилятору: "доверяй мне: я знаю, что это не выглядит как foo (это выглядит как что-то неизменяемое), но это так".

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

Предположим, у нас есть следующий код:

class CDerivedClass : public CMyBase {...};
class CMyOtherStuff {...};

CMyBase  *pSomething; // заполнено где-то

Теперь эти две строки компилируются одинаково:

CDerivedClass *pMyObject;
pMyObject = static_cast<CDerivedClass*>(pSomething); // Безопасно; если мы проверили

pMyObject = (CDerivedClass*)(pSomething); // То же, что и static_cast<>
                                     // Безопасно; если мы проверили
                                     // но труднее для чтения

Однако давайте рассмотрим почти идентичный код:

CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Ошибка компиляции: Нельзя преобразовать

pOther = (CMyOtherStuff*)(pSomething);            // Никакой ошибки компиляции.
                                                  // То же самое, что и reinterpret_cast<>
                                                  // и это неверно!!!

Как вы можете видеть, нет простого способа различить эти две ситуации, не зная много о всех вовлеченных классах.

Вторая проблема заключается в том, что найти приведения в стиле C слишком сложно. В сложных выражениях может быть очень трудно увидеть приведения в стиле C. Практически невозможно написать автоматизированный инструмент, который должен находить приведения в стиле C (например, инструмент для поиска), не имея полного компилятора C++ в качестве фронтенда. С другой стороны, легко искать "static_cast<" или "reinterpret_cast<".

pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
// Никакой ошибки компиляции.
// Но наличие reinterpret_cast<> 
// похоже на сирену с мигающими красными огнями в вашем коде.
// Само его введение должно вызывать у вас ОЧЕНЬ сильное беспокойство.

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

1

Один практичный совет: вы можете легко искать ключевое слово static_cast в вашем исходном коде, если планируете упорядочить проект.

0

Вопрос о том, когда использовать static_cast<> или С-стиль приведения типов, не такой простой, как кажется на первый взгляд. Существуют различные ситуации, в которых С-стиль приводит не к тем же результатам, что и C-операторы приведения типов. Операторы приведения в C созданы для того, чтобы сделать различные операции более явными.

На первый взгляд, static_cast<> и С-стиль приведения выглядят одинаково, например, когда мы приводим одно значение к другому:

int i;
double d = (double)i;                  // С-стиль приведения
double d2 = static_cast<double>(i);    // C++ приведение

Оба варианта приводят целочисленное значение к типу double. Однако при работе с указателями ситуация усложняется. Рассмотрим несколько примеров:

class A {};
class B : public A {};

A* a = new B;
B* b = (B*)a;                                  //(1) что здесь происходит?

char* c = (char*)new int(5);                   //(2) это нормально?
char* c1 = static_cast<char*>(new int(5));     //(3) ошибка компиляции

В данном примере (1) может сработать, если объект, на который указывает a, действительно является экземпляром класса B. Но что, если в этот момент кода вы не уверены, на что указывает a?

(2) может быть абсолютно законным (если вам нужно посмотреть только на один байт целого числа), но это также может быть ошибкой, и в таком случае было бы неплохо получить сообщение об ошибке, как в (3).

Операторы приведения в C++ призваны выявлять такие ошибки в коде, предоставляя сообщения об ошибках во время компиляции или выполнения, когда это возможно.

Таким образом, для строгого "приведения значений" вы можете использовать static_cast<>. Если вам нужно провести полиморфное приведение указателей во время выполнения, используйте dynamic_cast<>. Если вы действительно хотите забыть о типах, воспользуйтесь reinterpret_cast<>. А чтобы просто игнорировать const, существует const_cast<>.

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

0

static_cast означает, что вы не сможете случайно использовать const_cast или reinterpret_cast, что является положительным моментом.

0

C-стиль приведения типов может быть легко упущен в блоке кода. В то время как C++-стиль приведения является более предпочтительным, он также обеспечивает гораздо большую гибкость.

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

static_cast предлагает хорошее преобразование для числовых типов, например, от перечислений к целым числам или от целых к вещественным числам, а также для любых типов данных, в которых вы уверены в их типе. Он не выполняет никаких проверок во время выполнения.

В отличие от этого, dynamic_cast выполняет проверки и сигнализирует о любых неоднозначных присвоениях или преобразованиях. Он работает только с указателями и ссылками и имеет дополнительные затраты на производительность.

Существуют и другие, но это основные, с которыми вы столкнётесь.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь