Операции приведения типов в C++
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 28.12.16 13:39
Оценка: -2
В 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 г.
Re: Операции приведения типов в C++
От: Кодт Россия  
Дата: 28.12.16 14:24
Оценка: 18 (5)
Здравствуйте, 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;
}
Перекуём баги на фичи!
Re[2]: Операции приведения типов в C++
От: RussianFellow Россия http://russianfellow.livejournal.com
Дата: 17.01.17 13:36
Оценка:
Ясно, спасибо!

А что означает safe_cast, появившийся в последних стандартах C++ ?
1613 г. = 2024 г.
Re[3]: Операции приведения типов в C++
От: kov_serg Россия  
Дата: 17.01.17 13:53
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>Ясно, спасибо!


RF>А что означает safe_cast, появившийся в последних стандартах C++ ?

Тот же dynamic_cast только от микрософт, если nullptr кидает исключение InvalidCastException
Re[3]: Операции приведения типов в C++
От: _NN_ www.nemerleweb.com
Дата: 17.01.17 19:18
Оценка:
Здравствуйте, RussianFellow, Вы писали:

RF>Ясно, спасибо!


RF>А что означает safe_cast, появившийся в последних стандартах C++ ?


Это не в стандарте С++, а в расширении его (С++/CLI, C++/CX).
https://msdn.microsoft.com/en-us/23b7yy6w
http://rsdn.nemerleweb.com
http://nemerleweb.com
Re[4]: Операции приведения типов в C++
От: Кодт Россия  
Дата: 18.01.17 09:51
Оценка:
Здравствуйте, 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. Но если что, кину исключение
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.