Безопасность static_cast
AFAIK, для указателей/ссылок static_cast, если определение класса не видно компилятору в этой точке, тогда static_cast
будет вести себя как reinterpret_cast
.
Почему static_cast
небезопасно для указателей/ссылок и безопасно для числовых значений?
Ответы
Ответ 1
Короче говоря, из-за множественного наследования.
В длинном:
#include <iostream>
struct A { int a; };
struct B { int b; };
struct C : A, B { int c; };
int main() {
C c;
std::cout << "C is at : " << (void*)(&c) << "\n";
std::cout << "B is at : " << (void*)static_cast<B*>(&c) << "\n";
std::cout << "A is at : " << (void*)static_cast<A*>(&c) << "\n";
}
Вывод:
C is at : 0x22ccd0
B is at : 0x22ccd4
A is at : 0x22ccd0
Обратите внимание, что для правильной конвертации в B * static_cast должен изменить значение указателя. Если у компилятора не было определения класса для C, то он не знал бы, что B является базовым классом, и он, конечно же, не знает, какое смещение применять.
Но в ситуации, когда определение не видно, static_cast не ведет себя как reinterpret_cast, это запрещено:
struct D;
struct E;
int main() {
E *p1 = 0;
D *p2 = static_cast<D*>(p1); // doesn't compile
D *p3 = reinterpret_cast<D*>(p1); // compiles, but isn't very useful
}
Простой C-стиль, (B*)(&c)
делает то, что вы говорите: если видно, что структура C видна, показывая, что B является базовым классом, то это то же самое, что и static_cast. Если типы только декларируются только вперед, то это то же самое, что и reinterpret_cast. Это связано с тем, что он предназначен для совместимости с C, что означает, что он должен делать то, что C делает в случаях, которые возможны в C.
static_cast всегда знает, что делать для встроенных типов, это действительно то, что встроенные средства. Он может конвертировать int в float и т.д. Поэтому он всегда безопасен для числовых типов, но он не может преобразовать указатели, если (а) он не знает, на что они указывают, и (б) существует правильный вид отношений между указанными типами. Следовательно, он может преобразовать int
в float
, но не от int*
до float*
.
Как говорит AndreyT, есть способ, которым вы можете использовать static_cast
небезопасно, и компилятор, вероятно, не сохранит вас, потому что этот код является законным:
A a;
C *cp = static_cast<C*>(&a); // compiles, undefined behaviour
Одна из вещей, которые static_cast
может сделать, это "downcast" указатель на производный класс (в этом случае C является производным классом A). Но если referand не является фактически производным классом, вы обречены. A dynamic_cast
выполнит проверку во время выполнения, но для моего класса класса C вы не можете использовать dynamic_cast
, потому что у A нет виртуальных функций.
Аналогичным образом вы можете делать небезопасные вещи с static_cast
до и void*
.
Ответ 2
Нет, ваш "AFAIK" неверен. static_cast
никогда не ведет себя как reinterpret_cast
(за исключением, может быть, когда вы конвертируете в void *
, хотя это преобразование обычно не должно выполняться reinterpret_cast
).
Во-первых, когда static_cast
используется для преобразования указателя или ссылки, спецификация static_cast
явно требует определенного отношения между типами (и должно быть известно static_cast
). Для типов классов они должны быть связаны наследованием, как это воспринимается static_cast
. Это требование невозможно удовлетворить, если оба типа полностью не определены точкой static_cast
. Таким образом, если определение не отображается в точке static_cast
, код просто не будет компилироваться.
Чтобы проиллюстрировать вышеприведенные примеры: static_cast
можно использовать [избыточно] для выполнения upcasts указателя объекта. Код
Derived *derived = /* whatever */;
Base *base = static_cast<Base *>(derived);
компилируется только тогда, когда следующий код компилируется
Base *base(derived);
и для этого для компиляции определение обоих типов должно быть видимым.
Кроме того, static_cast
может использоваться для выполнения downcasts указателя объекта. Код
Base *base = /* whatever */;
Derived *derived = static_cast<Derived *>(base);
компилируется только тогда, когда следующий код компилируется
Base *base(derived); // reverse direction
и, опять же, для этого для компиляции определение обоих типов должно быть видимым.
Таким образом, вы просто не сможете использовать static_cast
с типами undefined. Если ваш компилятор позволяет это, это ошибка в вашем компиляторе.
static_cast
может быть небезопасным для указателей/ссылок для совершенно другой причины. static_cast
может выполнять иерархические downcasts для объектов указателя/ссылочных типов без проверки фактического динамического типа объекта. static_cast
также может выполнять иерархические повышения для типов указателей методов. Использование результатов этих непроверенных бросков может привести к поведению undefined, если это сделано без осторожности.
Во-вторых, когда static_cast
используется с арифметическими типами, семантика совершенно различна и
не имеет ничего общего с вышеизложенным. Он просто выполняет преобразования арифметического типа. Они всегда совершенно безопасны (в стороне от проблем с диапазоном), если они соответствуют вашим намерениям. На самом деле, это может быть хороший стиль программирования, чтобы избежать static_cast
для арифметических преобразований и вместо этого использовать статические C-стиль, чтобы обеспечить четкое различие в исходном коде между всегда безопасными арифметическими переводами и потенциально-опасным иерархическим указателем/ссылки.