AFAIK, для указателей/ссылок static_cast, если определение класса не видимо к компилятору в этой точке, то static_cast
будет ведут себя как reinterpret_cast
.
Почему static_cast
небезопасный для указателей/ссылок и безопасно для числовых значений?
Короче говоря, из-за множественного наследования.
В длинном:
#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
может «понижать» указатель на производный класс (в этом случае C является производным классом от A).Но если референдум не относится к производному классу, вы обречены. dynamic_cast
будет выполнять проверку во время выполнения, но для моего примера класса C вы не можете использовать dynamic_cast
, потому что A не имеет виртуальных функций.
Аналогичным образом можно делать небезопасные вещи с помощью static_cast
в и из void *
.
Нет, ваше "AFAIK" неверно. static_cast
никогда не ведет себя как reinterpret_cast
(за исключением, может быть, когда вы конвертируете в void *
, хотя это преобразование обычно не должно выполняться reinterpret_cast
).
Во-первых, когда static_cast
используется для преобразований указателей или ссылок, спецификация static_cast
явно требует, чтобы между типами существовала определенная связь (и она должна быть известна static_cast
). Для типов классов они должны быть связаны наследованием, как это воспринимается static_cast
.Невозможно удовлетворить это требование, если оба типа полностью не определены точкой static_cast
. Таким образом, если определение (определения) не отображается (а) в точке static_cast
, код просто не будет компилироваться.
Чтобы проиллюстрировать вышесказанное на примерах: static_cast
может использоваться [избыточно] для выполнения восходящего преобразования указателя объекта. Код
Derived *derived = /* whatever */;
Base *base = static_cast<Base *>(derived);
компилируется только тогда, когда следующий код компилируется
Base *base(derived);
, и для этого для компиляции определения обоих типов должны быть видимыми.
Кроме того, static_cast
может использоваться для выполнения понижающего преобразования указателя объекта. Код
Base *base = /* whatever */;
Derived *derived = static_cast<Derived *>(base);
компилируется только тогда, когда следующий код компилируется
Base *base(derived); // reverse direction
, и, опять же, для его компиляции определения обоих типов должны быть видимыми.
Таким образом, вы просто не сможете использовать static_cast
с неопределенными типами. Если ваш компилятор позволяет это, это ошибка в вашем компиляторе.
static_cast
может быть небезопасным для указателей / ссылок по совершенно другой причине. static_cast
может выполнять иерархическое понижающее преобразование для типов указателей / ссылок на объект без проверки фактического динамического типа объекта. static_cast
также может выполнять иерархические восходящие передачи для типов указателей методов. Использование результатов этих непроверенных приведений может привести к неопределенному поведению, если будет сделано без осторожности.
Во-вторых, когда static_cast
используется с арифметическими типами, семантика совершенно другая, и
не имеет ничего общего с вышеизложенным. Он просто выполняет арифметические преобразования типов.Они всегда совершенно безопасны (если не считать проблем с дальностью действия), если соответствуют вашим намерениям. На самом деле, неплохим стилем программирования было бы избегать static_cast
для арифметических преобразований и вместо этого использовать старые C-стиля приведения, просто чтобы обеспечить четкое различие в исходном коде между всегда безопасными арифметическими приведениями и потенциально - небезопасные иерархические приведения указателей / ссылок.