В C++ есть операции приведения типов (static_cast, dynamic_cast, reinterpret_cast, const_cast, safe_cast).
Что они означают, в чём их отличия друг от друга и от обычного преобразования типов (
Type1 a;
Type2 b;
b = (Type2)a;
или
Type1 *a;
Type2 *b;
// инициализация указателя a;
b=(Type2*)a;
) ?
1613 г. = 2024 г.
Здравствуйте, RussianFellow, Вы писали:
RF>В C++ есть операции приведения типов (static_cast, dynamic_cast, reinterpret_cast, const_cast, safe_cast).
RF>Что они означают, в чём их отличия друг от друга и от обычного преобразования типов (
(извините за неровный почерк, пишу с телефона во время собеседования, эйчар может спалить)
Да?
Ладненько. Если вкратце, то дело обстоит так:
Типы можно приводить тремя способами
— исходя из фактического типа данного объекта, — если это какой-то полиморфный класс
— исходя из формального типа, известного на момент компиляции, — сюда попадают и приведения к базовым и наследованным классам, и арифметические преобразования
— вообще наплевав на тип
Первое — это dynamic_cast. В C# ему соответствуют операторы is и as.
Неявно это происходит при вызове виртуальной функции: там сам механизм диспетчеризации вызова всю работу делает.
Явно — если нужно получить (или проверить) конкретный тип, имея указатель на базу
class A { .... };
class B : public A { .... };
class C : public A { .... };
void foo(A* a) {
if(dynamic_cast<B*>(a) != nullptr) { cout << "this is B"; }
};
Зачем? В тех случаях, когда на одном лишь переопределении виртуальных функций логику построить не удаётся. Например, получить доступ к функциям, объявленным только у наследника.
Замена dynamic_cast'у — рукодельные функции
class B;
class C;
class A {
virtual B* as_B() { return nullptr; }
virtual C* as_C() { return nullptr; }
....
};
class B : public A {
virtual B* as_B() { return this; }
....
};
class C : public B {
virtual C* as_C() { return this; }
};
Уйма писанины и жёстко зафиксированная иерархия без возможности расширения, зато есть гарантия, что никто не попробует кастить то, что не предусмотрено дизайном.
Второе — это static_cast и const_cast.
static_cast — явная альтернатива всем неявным преобразованиям: от наследника к базе и наоборот (этого неявного преобразования нет, тут просто необходимо делать static_cast), арифметика, пользовательские приведения типов.
Обычно static_cast пишут тогда, когда неявное преобразование запрещено (от базы к наследнику) или неоднозначно: например, надо выполнить цепочку преобразований.
class A;
class B { operator A() const; };
class C { operator B() const; };
class D { D(A); };
void foo(D);
C c;
foo(c); // нельзя
foo((D)(A)(B)c); // можно. здесь вместо static_cast я для краткости написал C-style
const_cast — особый случай, это единственный оператор, позволяющий накладывать и снимать квалификаторы const и volatile.
Сделано из соображений безопасности, чтобы случайно не разлочить константный объект явным приведением другого рода. dynamic_cast и static_cast не имеют право снимать константность.
Правда, это не очень удобно, так как const_cast должен явно указать тип
const int& f(); // пусть у нас есть какое-то выражение f() возвращающее константную ссылку или указатель
auto& x = f(); // неявное снятие константности запрещено
auto& x = const_cast<auto&>(f()); // нет такого синтаксиса
auto& x = const_cast<int&>(f()); // нужно знать тип - int
auto& x = const_cast<std::remove_const_t<decltype(f())>>(f()); // можно, но громоздко, и дублируется выражение
// выкручиваются так
template<class T> remove_const_ref(const T& t) { return const_cast<T&>(t); }
template<class T> remove_const_ptr(const T* t) { return const_cast<T*>(t); }
auto& x = remove_const_ref(f());
Третье — reinterpret_cast.
Тупо взять пачку байтов и сказать: "это — вот это!"
Бывает нужно для всяких низкоуровневых действий, — например, передаче данных через файл или по сети. Или для трюков.
https://en.wikipedia.org/wiki/Fast_inverse_square_root
float Q_rsqrt( float number ) // формула Кармака - быстрый обратный корень
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = reinterpret_cast<long&>(y); // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = reinterpret_cast<float&>(i);
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}
1613 г. = 2024 г.
Здравствуйте, RussianFellow, Вы писали:
RF>Ясно, спасибо!
RF>А что означает safe_cast, появившийся в последних стандартах C++ ?
Это не в стандарте С++, а в расширении его (С++/CLI, C++/CX).
https://msdn.microsoft.com/en-us/23b7yy6w
Здравствуйте, kov_serg, Вы писали:
RF>>А что означает safe_cast, появившийся в последних стандартах C++ ?
_>Тот же dynamic_cast только от микрософт, если nullptr кидает исключение InvalidCastException
Тот же dynamic_cast от ссылочного типа кидает std::bad_cast
Base* pb;
.....
if (auto* pd = dynamic_cast<Derived*>(pb)) { // если не подсунули и если не получили nullptr
do_smth_with_derived(pd);
}
.....
Base& rb = *pb; // мамой клянус, тут не nullptr. Проверок нет, ССЗБ.
do_smth_with_derived(dynamic_cast<Derived&>(rb)); // мамой клянус, тут Derived. Но если что, кину исключение