C-style cast

#новичкам



Как уже неоднократно было нами отмечено, что язык C++ разрабатывался с поддержкой обратной совместимости языка C. В частности, в C++ поддерживается приведение в стиле C:

int value = (int)arg;



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



Вот давайте вспомним все операторы приведения, про которые мы успели рассказать? У нас были посты про:

- static_cast

- reinterpret_cast

- const_cast

- dynamic_cast



У каждого из них есть своя область применения и соответствующий алгоритм приведения, а так же наборы проверок! Т.к. C-style cast сочетает в себе все вышеперечисленные операторы, то большая часть проверок просто отсутствует... Они не проверяют конкретный случай, что является очень опасным моментом.



В случае невозможности желаемого приведения, C-style cast совершит другое подходящее. Рассмотрим ошибку из живого примера:

cpp

using PPrintableValue = PrintableValue *;

...

auto data = (PPrintableValue)value;



Мы хотели привести value к типу PrintableValue (int64_t -> int32_t). Но в результате неудачного нейминга псевдонима мы ошиблись. Вдруг клавиша P залипла просто? Вдруг рефакторинг неудачно прошел? В итоге мы собрали программу, смогли её запустить и привели int64_t к int32_t*, а дальше его попытались разыменовать. На первый взгляд, ошибка непонятна: мы ожидали static_cast, а получили reinterpret_cast. В больших продуктах такие ошибки могут оставаться незамеченными, пока не будет проведено полное тестирование продукта (вами или клиентом).



Давайте вспомним про приведение между ветками ромбовидного наследования из статьи про dynamic_cast. Использование C-style приведения бездумно выполнит то, что от него попросили и вляпается в ошибку, хоть красненьким и не подчеркивается :) На самом деле он выполнит reinterpret_cast, но это логическая ошибка! Нам очевидно, что этот оператор не подходит по смыслу, но может подойти static_cast. Если мы попробуем это сделать, будет ошибка компиляции:

error: invalid 'static_cast' from type 'Mother*' to type 'Father*':

Father *switched_son_of_father = static_cast<Father*>(son_of_mother);



Опустим тему с const_cast, думаю, тут и так все понятно.



И вот ладно, дело во внимательности и понимании предназначения операторов... C-style cast позволяет выполнить приведение к приватным предкам класса: живой пример. Вот от вас намеренно хотели скрыть возможность вмешательства в поведение предка, а вы это ограничение обошли и даже не заметили подвоха. Увидеть это на ревью так же сложно! Это ведет к очень забагованному поведению программы.



Оператор C-style cast скрывает в себе достаточно неочевидное поведение в некоторых ситуациях. Его сложно заметить, его сложно отлаживать. Возможно, что будет проще отказаться от него вовсе, чем помнить о всех подводных камнях. Предупреждения вам в помощь! Добавляйте опцию компилятора:

-Wold-style-cast



#cppcore #goodpractice