Навіщо використовувати static_cast <int> (x) замість (int) x?


667

Я чув, що цю static_castфункцію слід віддавати перевагу стилю С або простому кастингу в стилі функції. Це правда? Чому?


36
Заперечуйте проти вашої честі, запитуйте і відповідайте .
Graeme Perrow

25
Я не погоджуюся, це інше питання стосувалося опису відмінностей між введеннями в програму C ++. Це питання стосується реальної корисності static_cast, яка дещо відрізняється.
Вінсент Роберт

2
Ми, безумовно, могли б об'єднати два питання, але те, що нам потрібно було б зберегти від цього потоку, - це перевага використання функцій над кастингом у стилі C, який наразі згадується лише у відповіді на один рядок в іншій темі, без голосів .
Томмі Герберт

6
Це питання стосується "вбудованих" типів, як int, тоді як це питання про типи класів. Це здається досить суттєвою різницею, щоб заслужити окреме пояснення.

9
static_cast насправді є оператором, а не функцією.
ThomasMcLeod

Відповіді:


633

Основна причина полягає в тому , що класичні зліпки C не роблять жодної різниці між тим, що ми називаємо static_cast<>(), reinterpret_cast<>(), const_cast<>()і dynamic_cast<>(). Ці чотири речі абсолютно різні.

A, static_cast<>()як правило, безпечний. Існує дійсна конверсія в мові або відповідний конструктор, що робить це можливим. Єдиний раз, коли це трохи ризиковано - це коли ти перейшов до спадкового класу; ви повинні переконатися, що об’єкт насправді є нащадком, який ви стверджуєте, що він є, за допомогою зовнішньої мови (як прапор об’єкта). A dynamic_cast<>()безпечний, доки перевіряється результат (покажчик) або враховується можливий виняток (посилання).

А reinterpret_cast<>()(або а const_cast<>()) з іншого боку - це завжди небезпечно. Ви скажете компілятору: "повірте мені: я знаю, це не схоже на foo(це виглядає так, ніби він не змінюється), але це так".

Перша проблема полягає в тому, що майже неможливо сказати, який з них відбуватиметься у складі C-стилю, не дивлячись на великі та розбіжні фрагменти коду та не знаючи всіх правил.

Припустимо наступне:

class CDerivedClass : public CMyBase {...};
class CMyOtherStuff {...} ;

CMyBase  *pSomething; // filled somewhere

Тепер ці два складені однаково:

CDerivedClass *pMyObject;
pMyObject = static_cast<CDerivedClass*>(pSomething); // Safe; as long as we checked

pMyObject = (CDerivedClass*)(pSomething); // Same as static_cast<>
                                     // Safe; as long as we checked
                                     // but harder to read

Однак подивимось цей майже однаковий код:

CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Compiler error: Can't convert

pOther = (CMyOtherStuff*)(pSomething);            // No compiler error.
                                                  // Same as reinterpret_cast<>
                                                  // and it's wrong!!!

Як бачите, не існує простого способу розрізнити дві ситуації, не знаючи багато про всі заняття.

Друга проблема полягає в тому, що ролі в стилі С занадто важко знайти. У складних виразах можна дуже важко побачити ролі в стилі С. Практично неможливо написати автоматизований інструмент, який повинен знаходити касти у стилі C (наприклад, інструмент пошуку) без повного виду компілятора C ++. З іншого боку, легко знайти "static_cast <" або "reinterpret_cast <".

pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
      // No compiler error.
      // but the presence of a reinterpret_cast<> is 
      // like a Siren with Red Flashing Lights in your code.
      // The mere typing of it should cause you to feel VERY uncomfortable.

Це означає, що ролі в стилі С не тільки небезпечніші, але набагато складніше знайти їх усіх, щоб переконатися в правильності їх виконання.


30
Ви не повинні використовувати static_castдля скидання спадкової ієрархії, а навпаки dynamic_cast. Це поверне або нульовий покажчик, або дійсний вказівник.
Девід Торнлі

41
@David Thornley: Я згоден, як правило. Думаю, я вказав на застереження, які слід використовувати static_castу цій ситуації. dynamic_castможе бути безпечнішим, але це не завжди найкращий варіант. Іноді ви знаєте, що вказівник вказує на даний підтип, непрозорий для компілятора, а a static_castшвидше. Принаймні в деяких середовищах dynamic_castпотрібна додаткова підтримка компілятора та вартість виконання (увімкнення RTTI), і ви, можливо, не захочете включити його лише за пару перевірок, які ви можете зробити самостійно. RTTI C ++ - це лише одне можливе рішення проблеми.
Євро Міцеллі

18
Ваша вимога щодо C-ролей помилкова. Усі ролі C - це конверсії значень, приблизно порівнянні з C ++ static_cast. Еквівалент С - reinterpret_castце *(destination_type *)&взяття адреси об'єкта, перекидання цієї адреси на вказівник на інший тип, а потім перенаправлення. За винятком випадків, в разі символьних типів або певних типів структури , для яких С визначає поведінку цієї конструкції, це зазвичай призводить до непередбачуваного поведінки в C.
R .. GitHub СТОП допоміг ICE

11
Ваша тонка відповідь стосується основної публікації. Я шукав відповідь на заголовок "чому використовувати static_cast <int> (x) замість (int) x". Тобто, для типу intintпоодинці), чому використання static_cast<int>порівняно (int)як єдиної переваги, здається, має змінні та покажчики класів. Попросіть детально розібратися з цим.
chux

31
@chux, int dynamic_castне застосовується, але всі інші причини стоять. Наприклад: скажімо v, параметр функції оголошено як float, тоді (int)vє static_cast<int>(v). Але якщо ви зміните параметр на float*, (int)vтихо стає, reinterpret_cast<int>(v)поки static_cast<int>(v)це незаконно і правильно потрапив компілятор.
Euro Micelli

115

Одна прагматична порада: ви можете легко шукати ключове слово static_cast у своєму вихідному коді, якщо плануєте налагодити проект.


3
Ви можете шукати, використовуючи дужки, хоча такі, як "(int)", але хороша відповідь і поважна причина використовувати кастинг у стилі C ++.
Майк

4
@Mike, який знайде помилкові позитиви - оголошення функції з одним intпараметром.
Натан Осман

1
Це може призвести до помилкових негативів: якщо ви шукаєте базу коду, де ви не єдиний автор, ви не знайдете касти в стилі C, які інші могли ввести з якихось причин.
Руслан

7
Як би це допомогло налагодити проект?
Білоу

Ви б не шукали static_cast, оскільки це, швидше за все, правильний. Ви хочете відфільтрувати static_cast, шукаючи reinterpret_cast, const_cast і, можливо, навіть, динамічний_cast, оскільки вони вказували б місця, які можна переробити. C-лиття змішується разом, і це не дає вам причин для кастингу.
Драган

78

Коротше кажучи :

  1. static_cast<>() дає змогу перевіряти час компіляції, C-Style cast не робить.
  2. static_cast<>()їх можна легко помітити в будь-якому місці всередині вихідного коду C ++; на відміну, C_Style ролі важче помітити.
  3. Наміри передаються набагато краще, використовуючи ролі C ++.

Більше пояснення :

Статичний формат виконує перетворення між сумісними типами . Він схожий на ролі в стилі С, але є більш обмежуючим. Наприклад, команда в стилі C дозволить цілому вказівнику вказувати на знак.

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

Оскільки це призводить до того, що 4-байтний вказівник вказує на 1 байт виділеної пам'яті, запис у цей покажчик або спричинить помилку під час виконання, або замінить деяку суміжну пам'ять.

*p = 5; // run-time error: stack corruption

На відміну від набору C-стилю, статичний виступ дозволить компілятору перевірити, чи типи даних вказівника та покажчика сумісні, що дозволяє програмісту вловлювати це неправильне призначення вказівника під час компіляції.

int *q = static_cast<int*>(&c); // compile-time error

Детальніше про:
Яка різниця між static_cast <> та литтям у стилі C
та
Регулярним складанням проти static_cast проти динамичного_cast


21
Я не погоджуюся, що static_cast<>()це читабельніше. Я маю на увазі, іноді так і є, але більшість часу - особливо це стосується основних цілих типів - це просто жахливо і непотрібно багатослівне. Наприклад: Це функція, яка замінює байти 32-бітного слова. Це було б майже неможливо читати, використовуючи static_cast<uint##>()касти, але це досить легко зрозуміти, використовуючи (uint##)касти. Зображення коду: imgur.com/NoHbGve
Todd Lehman

3
@ToddLehman: Дякую, але я alwaysтеж не сказав . (але більшість випадків так) Є певні випадки, коли акторський стиль стилю набагато читає. Це одна з причин, коли кастинг стилю досі живе і б'є в c ++ imho. :) До речі, це був дуже приємний приклад
Ріка

8
Код @ToddLehman у цьому зображенні використовує два касти, пов'язані ланцюгом ( (uint32_t)(uint8_t)), щоб досягти скидання байтів, окрім найнижчих. Для цього є порозрядне і ( 0xFF &). Використання ролях заперечує наміри.
Öö Tiib

28

Питання є більш простим, ніж просто використання камер в стилі static_cast або C, оскільки різні випадки трапляються при використанні кастингу в стилі C. Оператори лиття C ++ мають на меті зробити ці операції більш чіткими.

На поверхні static_cast та C стилі відображаються однаково, наприклад, при передачі одного значення іншому:

int i;
double d = (double)i;                  //C-style cast
double d2 = static_cast<double>( i );  //C++ cast

Обидва вони приводять ціле значення до подвійного. Однак при роботі з покажчиками справи ускладнюються. кілька прикладів:

class A {};
class B : public A {};

A* a = new B;
B* b = (B*)a;                                  //(1) what is this supposed to do?

char* c = (char*)new int( 5 );                 //(2) that weird?
char* c1 = static_cast<char*>( new int( 5 ) ); //(3) compile time error

У цьому прикладі (1), можливо, гаразд, тому що об'єкт, на який вказує A, є дійсно екземпляром B. Але що робити, якщо ви не знаєте в той момент коду, що насправді вказує? (2) може бути абсолютно законним (ви хочете подивитися лише на один байт цілого числа), але це також може бути помилкою, в цьому випадку помилка була б непоганою, як (3). Оператори кастингу C ++ призначені для викриття цих проблем у коді, надаючи помилки під час компіляції або час виконання під час виконання.

Отже, для суворого "кастингу значення" можна використовувати static_cast. Якщо ви хочете виконувати поліморфне лиття покажчиків під час виконання, використовуйте динамічну передачу. Якщо ви дійсно хочете забути про типи, ви можете використовувати reintrepret_cast. А щоб просто викинути const у вікно, є const_cast.

Вони просто роблять код більш явним, щоб він виглядав так, що ви знаєте, що робили.


26

static_castозначає, що ви не можете випадково const_castабо reinterpret_cast, що добре.


4
Додаткові (хоч і досить незначні) переваги в порівнянні зі стилем C полягають у тому, що він більше виділяється (робити щось потенційно погано повинно виглядати потворно) і він є більш шаленим.
Майкл Берр

4
grep-здатність - це завжди плюс, в моїй книзі.
Бранан

7
  1. Дозволяє легко знаходити касти у вашому коді за допомогою grep або подібних інструментів.
  2. Зрозуміло, що саме ви робите роль, і залучаєте допомогу компілятора в його застосуванні. Якщо ви хочете лише відкинути const-ness, тоді ви можете використовувати const_cast, що не дозволить вам робити інші типи перетворень.
  3. Касти самі по собі некрасиві - ви як програміст перекручуєте, як компілятор звичайно ставиться до вашого коду. Ви говорите компілятору: "Я знаю краще за вас". У цьому випадку має сенс, що виконання акторської ролі має бути помірно болючою справою, і вони повинні дотримуватися у вашому коді, оскільки вони є ймовірним джерелом проблем.

Див. Ефективне введення C ++


Я повністю погоджуюся з цим для класів, але чи має сенс використання стилів C ++ для типів POD?
Захарій Краус

Я думаю так. Усі 3 причини стосуються ПОД, і корисно мати лише одне правило, а не окремі для класів та ПДД.
JohnMcG

Цікаво, що, можливо, мені доведеться змінити, як я роблю свої функції в майбутньому коді для типів POD.
Захарій Краус

7

Йдеться про те, яку безпеку типу ви хочете нав'язати.

Коли ви пишете (bar) foo(що еквівалентноreinterpret_cast<bar> foo тому, що ви не надали оператора перетворення типів), ви кажете компілятору ігнорувати безпеку типу та просто виконайте так, як йому сказано.

Коли ви пишете, static_cast<bar> fooви просите компілятора хоча б перевірити, чи є сенс перетворення типу, а для цілісних типів - вставити якийсь код перетворення.


EDIT 2014-02-26

Я написав цю відповідь більше 5 років тому, і я зрозумів її неправильно. (Див. Коментарі.) Але це все ще отримує відгуки!


8
(bar) foo не є рівнозначним reinterpret_cast <bar> (foo). Правила для "(TYPE) expr" полягають у тому, що він вибере відповідний стиль C ++, який використовується для використання, який може включати reinterpret_cast.
Річард Корден

Гарна думка. Євро Міцеллі дав остаточну відповідь на це питання.
Пітару

1
Також так і є static_cast<bar>(foo), з дужками. Те саме для reinterpret_cast<bar>(foo).
LF

6

У ролях коду легко пропустити касти в стилі. У ролях стилів C ++ - це не лише краща практика; вони пропонують набагато більшу ступінь гнучкості.

reinterpret_cast дозволяє інтегрувати конверсії типу вказівника, проте при неправильному використанні може бути небезпечним.

static_cast пропонує хорошу конверсію для числових типів, наприклад, від перерахунків до int чи int до плаваючих чи будь-яких типів даних, яким ви впевнені. Він не виконує жодних перевірок часу виконання.

З іншого боку, динамічна передача виконає ці перевірки, позначивши будь-які неоднозначні завдання чи перетворення. Він працює лише за покажчиками та посиланнями і несе накладні витрати.

Є кілька інших, але це головні, з якими ви зіткнетесь.


4

static_cast, крім маніпулювання покажчиками на класи, також може використовуватися для виконання перетворень, чітко визначених у класах, а також для здійснення стандартних перетворень між основними типами:

double d = 3.14159265;
int    i = static_cast<int>(d);

4
Чому б хтось писав static_cast<int>(d), коли (int)dце набагато більш стисло і читабельно? (Я маю на увазі у випадку основних типів, а не на об’єктні покажчики.)
Тодд Леман,

@ gd1 - Чому хтось поставить узгодженість вище читабельності? (фактично наполовину серйозний)
Тодд Леман

2
@ToddLehman: Мені, враховуючи, що робити виняток для певних типів тільки тому, що вони для вас якимось особливим, для мене немає сенсу, і я також не погоджуюся з вашим самим поняттям читабельності. Коротше не означає більш читабельного, як я бачу з зображення, яке ви розмістили в іншому коментарі.
gd1

2
static_cast - це чітке і свідоме рішення зробити дуже конкретний вид перетворення. Тому це додає ясності намірів. Це також дуже зручно як маркер для пошуку вихідних файлів для конверсій у процесі перегляду коду, помилки чи оновлення.
Персикст

1
@ToddLehman контрапункт: Чому б хто писав, (int)dколи int{d}набагато читає? Конструктор, або подібний до функцій (), синтаксис не так швидко перетворюється на кошмарний лабіринт дужок у складні вирази. У цьому випадку це було б int i{d}замість int i = (int)d. Набагато краще ІМО. Однак, коли мені просто потрібен тимчасовий вираз, я використовую static_castі ніколи не використовую конструкторські роли, я не думаю. Я використовую лише (C)castsтоді, коли поспішаю писати налагодження couts ...
underscore_d
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.