Я слышал, что функция static_cast должна быть предпочтительнее преобразования в стиле C или простого преобразования в стиле функции. Это правда? Почему?
Я не согласен, этот другой вопрос касался описания различий между приведениями, вводимыми в C++. Этот вопрос касается реальной полезности static_cast, который немного отличается.
Мы, конечно, могли бы объединить два вопроса, но что нам нужно сохранить из этого потока, так это преимущество использования функций по сравнению с приведением в стиле C, которое в настоящее время упоминается только в однострочном ответе в другом потоке, без голосов .
Голосование "за", потому что эта ссылка по-прежнему предоставляет полезную информацию.
Этот вопрос касается «встроенных» типов, таких как int, тогда как этот вопрос касается типов классов. Это кажется достаточно существенной разницей, чтобы заслужить отдельного объяснения.
static_cast на самом деле является оператором, а не функцией.
@ThomasMcLeod Операторы - это особый тип функций (по крайней мере, я так думал)
@DarthRubik, на самом деле вызовы функций - это тип оператора. en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B
@ user153275 хорошо, заголовок посвящен примитивным типам, но вопрос отбрасывает его. И ответы тоже. Я не убежден изменить свой образ жизни. Уверен, что с int x = (int)myfloat; все будет в порядке, и он сделает то, что я от него ожидаю.





static_cast, помимо управления указателями на классы, также может использоваться для выполнения преобразований, явно определенных в классах, а также для выполнения стандартных преобразований между фундаментальными типами:
double d = 3.14159265;
int i = static_cast<int>(d);
Но зачем кому-то писать static_cast<int>(d), если (int)d намного более лаконичен и удобочитаем? (Я имею в виду в случае базовых типов, а не указателей на объекты.)
@ gd1 - Зачем кому-то ставить согласованность выше удобочитаемости? (на самом деле полусерьезно)
@ToddLehman: Я, учитывая, что создание исключения для определенных типов только потому, что они для вас особенные, для меня не имеет никакого смысла, и я также не согласен с вашим понятием читабельности. Короче не означает более читабельный, как я вижу из изображения, которое вы разместили в другом комментарии.
static_cast - это четкое и осознанное решение о совершении очень определенного типа преобразования. Следовательно, это добавляет ясности намерениям. Это также очень удобно в качестве маркера для поиска исходных файлов на предмет преобразований при проверке кода, ошибках или обновлении.
Контрапункт @ToddLehman: Зачем кому-то писать (int)d, когда int{d} намного удобнее для чтения? Конструкторский или функциональный, если у вас (), синтаксис не так быстр, чтобы превратиться в кошмарный лабиринт круглых скобок в сложных выражениях. В этом случае это будет int i{d} вместо int i = (int)d. Гораздо лучше ИМО. Тем не менее, когда мне просто нужно временное значение в выражении, я использую static_cast и никогда не использовал преобразование конструктора, я не думаю. Я использую только (C)casts, когда наспех пишу отладочные cout ...
static_cast означает, что вы не можете случайно const_cast или reinterpret_cast, и это хорошо.
Дополнительные (хотя и незначительные) преимущества по сравнению с приведением стиля C заключаются в том, что он выделяется больше (выполнение чего-то потенциально плохого должно выглядеть некрасиво) и более способно работать с grep.
В моей книге grep-способность всегда является плюсом.
Речь идет о том, насколько безопасны типы вы хотите наложить.
Когда вы пишете (bar) foo (что эквивалентно reinterpret_cast<bar> foo, если вы не предоставили оператор преобразования типа), вы говорите компилятору игнорировать безопасность типов и просто делать то, что сказано.
Когда вы пишете static_cast<bar> foo, вы просите компилятор хотя бы проверить, что преобразование типов имеет смысл, а для целочисленных типов - вставить некоторый код преобразования.
РЕДАКТИРОВАТЬ 2014-02-26
Я написал этот ответ более 5 лет назад и ошибся. (См. Комментарии.) Но он все равно получает положительные голоса!
(bar) foo не эквивалентно reinterpret_cast <bar> (foo). Правила для "(TYPE) expr" заключаются в том, что он будет выбирать соответствующее приведение стиля C++ для использования, которое может включать reinterpret_cast.
Хорошая точка зрения. Евро Мичелли дал окончательный ответ на этот вопрос.
Также это static_cast<bar>(foo), в скобках. То же для reinterpret_cast<bar>(foo).
Один практический совет: вы можете легко найти ключевое слово static_cast в исходном коде, если планируете привести проект в порядок.
вы также можете выполнять поиск, используя скобки, например, "(int)", но это хороший ответ и веская причина для использования приведения в стиле C++.
@Mike, который обнаружит ложные срабатывания - объявление функции с одним параметром int.
Это может дать ложноотрицательные результаты: если вы ищете кодовую базу, в которой вы не единственный автор, вы не найдете приведений в стиле C, которые по некоторым причинам могли быть введены другими.
Как это поможет привести в порядок проект?
Вы не стали бы искать static_cast, потому что это, скорее всего, правильный. Вы хотите отфильтровать static_cast, пока вы ищете reinterpret_cast, const_cast и, возможно, даже dynamic_cast, поскольку они будут указывать на места, которые можно перепроектировать. C-cast смешивает все вместе и не дает вам повода для кастинга.
Основная причина заключается в том, что классические модели C не делают различий между тем, что мы называем static_cast<>(), reinterpret_cast<>(), const_cast<>() и dynamic_cast<>(). Эти четыре вещи совершенно разные.
static_cast<>() обычно безопасен. В языке есть допустимое преобразование или соответствующий конструктор, который делает это возможным. Единственный раз, когда это немного рискованно, - это когда вы переходите к унаследованному классу; вы должны убедиться, что объект на самом деле является потомком, которого вы утверждаете, с помощью внешних по отношению к языку средств (например, флага в объекте). 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. Практически невозможно написать автоматизированный инструмент, который должен находить приведение типов в стиле 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.
Это означает, что забросы в стиле C не только более опасны, но и гораздо сложнее найти их все, чтобы убедиться, что они верны.
Вы не должны использовать static_cast для отбрасывания иерархии наследования, а скорее dynamic_cast. Это вернет либо нулевой указатель, либо действительный указатель.
@ Дэвид Торнли: Обычно я согласен. Думаю, я указал на предостережения относительно использования static_cast в этой ситуации. dynamic_cast может быть безопаснее, но это не всегда лучший вариант. Иногда вы делаете знать, чтобы указатель указывал на данный подтип, непрозрачным для компилятора средствами, и static_cast работает быстрее. По крайней мере, в некоторых средах dynamic_cast требует дополнительной поддержки компилятора и затрат времени выполнения (включение RTTI), и вы можете не захотеть включать его только для пары проверок, которые вы можете выполнить самостоятельно. RTTI в C++ - лишь одно из возможных решений проблемы.
Ваше утверждение о приведении C является ложным. Все преобразования C являются преобразованиями значений, примерно сопоставимыми с C++ static_cast. Эквивалент reinterpret_cast в языке C - это *(destination_type *)&, то есть получение адреса объекта, преобразование этого адреса в указатель на другой тип и последующее разыменование. За исключением символьных типов или определенных структурных типов, для которых C определяет поведение этой конструкции, это обычно приводит к неопределенному поведению в C.
Ваш точный ответ относится к тексту сообщения. Я искал ответ на заголовок «зачем использовать static_cast <int> (x) вместо (int) x». То есть для типа int (и только int) зачем использовать static_cast<int> вместо (int), поскольку единственное преимущество, по-видимому, заключается в переменных и указателях класса. Попросите вас подробнее рассказать об этом.
@chux, для intdynamic_cast не применяется, но все остальные причины остаются в силе. Например: предположим, что v - это параметр функции, объявленный как float, тогда (int)v - это static_cast<int>(v). Но если вы измените параметр на float*, (int)v незаметно станет reinterpret_cast<int>(v), в то время как static_cast<int>(v) будет недопустимым и правильно пойман компилятором.
@EuroMicelli: Однажды меня укусила описанная вами ситуация. Я преобразовал double* в int, когда считал, что это double. На отладку у меня ушло полдня, и с тех пор я любой ценой избегаю приведений в стиле C.
Все ваши примеры предназначены для классов и очень хорошо объясняют, почему программисты должны абсолютно использовать операторы приведения C++. У меня есть проблема: что вы делаете, когда имеете дело с типом пода, таким как int * to void * или void * to int *? В этой ситуации я использую c-приведения или операторы приведения C++? Я мог бы добавить это как вопрос в другой пост. Не уверен, что это здесь.
Хотя неясно, должен ли вопрос быть о приведении типов в стиле C++ в целом или об арифметических приведениях в частности, странно видеть, что в этом вопросе в качестве примера используется то, что кажется арифметическим приведением, в то время как принятый ответ полностью игнорирует арифметические преобразования и фокусируется исключительно на иерархических приведениях указателей. (???)
Фактически можно утверждать, что приведение типов в стиле C++ должно быть зарезервировано для приведения иерархических указателей и различных операций взлома, в то время как классические приведения в стиле C следует использовать для арифметических приведений. Это обеспечило бы четкое различие между этими двумя (или тремя) довольно разными мирами слепков. Другими словами, когда вы выполняете арифметическое приведение конкретно к int, на самом деле подходит (int) x, а не static_cast<int>(x).
@AndreyT Существует разница между приведением стиля C и static_cast ... а именно const часть (если мы говорим об арифметическом приведении), которое в C не так актуально, но в C++ компилятор выполняет гораздо больше. .. И может привести к неопределенному поведению, если вы не будете осторожны.
@AndreyT Возможно, вы правы. Однако использование приведения в стиле C++ добавляет компилятору обнаружение случаев, когда программист случайно использует неправильное приведение (например, использовался static_cast<>, но требуется reinterpret_cast<>). Нет реальной причины отказываться от этого. И нет, это включает приведение к (int), потому что вы не хотите, чтобы компилятор молча применял reinterpret_cast<int> после того, как вы реорганизовали некоторый код).
@rubenvb: Я не понимаю, о чем вы говорите. Вы, должно быть, что-то путаете. const вообще не играет роли в арифметических приведениях. Вопрос const относится только к приведению указателя / ссылки, которое не является арифметическим. Итак, какой смысл вы пытаетесь подчеркнуть, упомянув здесь const, и как это связано с тем, что я сказал выше, мне не ясно.
@sehe: Если рефакторинг настолько серьезен, что может превратить арифметическое приведение в приведение по-новому, то в любом случае потребуется тщательная проверка кода, которая будет включать в себя гораздо больше, чем обнаружение и исправление таких приведений (при условии, что существует даже заслуживающий доверия вариант использования).
Влияние дополнительного образа мыслей, обеспечиваемого static_cast в таких случаях, на мой взгляд, несущественно по сравнению с положительным влиянием на читаемость кода, обеспечиваемым более элегантными арифметическими приведениями и визуальным разделением арифметических приведений от иерархических / хакерских. Я считаю, что последний эффект значительно более ценен. Поэтому я придерживаюсь приведений в стиле C для арифметических преобразований: вознаграждение намного превышает риск.
@AndreyT, и дело в том, что тщательная проверка кода может легко упустить эти вещи именно потому, что им не хватает многословности - намерение не заявлено однозначно. Это приведет к ошибке.
@sehe: О, когда рефакторинг настолько радикален, тщательная проверка кода может упустить многое, среди которых этот конкретный вопрос вообще не играет большой роли. Возвращаясь к теме: другими словами, даже при том, что они хотели разделить разные типы приведений, они не пошли достаточно далеко. static_cast все еще переполнен. Мне нужен специальный состав для арифметических преобразований. И мне это так необходимо, что я рискну использовать приведение в стиле C для арифметических преобразований только для того, чтобы мои арифметические преобразования выглядели иначе.
@AndreyT Ничто не мешает вам делать что-то вроде integer_cast<>, который делает все, что вам нужно (я думаю, что для этого может быть что-то в Boost Integer). Подобные вещи могут уже существовать (со специализированными библиотеками, такими как SafeInt)
you must make sure that the object is a actually the descendant that you claim it is, by means external to the language (like a flag in the object). Я не согласен (кроме того факта, что член в объекте не является «внешним по отношению к языку»): если программа правильно спроектирована, вполне возможно узнать, что объект имеет правильный тип, и пометить это тратит впустую пространство. Просто убедитесь, что вы знаете свои экземпляры, если вы используете static_cast, и убедитесь, что assert() на эквивалентном dynamic_cast при запуске отладочных сборок.
@jterm Что? Нет ничего небезопасного, если экземпляр, приведенный к "дочернему" является, является дочерним, а делает имеет те члены, о которых знает хороший программист. (И, в идеале, assert( dynamic_cast ... ) в отладочных сборках)
@underscore_d, под «внешним по отношению к языку» я имею в виду, что язык не определяет значение любого такого флага (компилятор не знает, что он означает, и не может его проверить). «Я знаю, что делаю», как вы указываете, конечно, совершенно верно и может спасти вас от использования флага в зависимости от программы. Это тоже «внешнее по отношению к языку» :-).
«нет различия между тем, что мы называем static_cast <> (), reinterpret_cast <> (), const_cast <> () и dynamic_cast <>» «различие» состоит в том, что не существует такой вещи, как dynamic_cast<>, выполненный с помощью приведения в стиле C или синтаксиса приведения в функциональном стиле.
dynamic_cast<>() не имеет отношения к приведению в стиле c.
Другая ситуация, когда static_cast может быть рискованным, заключается в том, что static_cast позволяет преобразовать любой тип указателя в void* и void* в любой тип указателя, что позволит вам, например, преобразовать char* в void*, а затем в int*.
Хорошей практикой является использование ключевого слова, определенного макросом, для поиска приведений в c-файлах.
const_cast<>() [...] всегда опасен» - not so, only if used to удалятьconst or volatile. const_cast<T const volatile&>(t) is always safe, and const_cast<T const&>(t) is safe provided t is not volatile.
@DonaldDuck "что позволит вам, например, использовать" Если вы имеете дело с void* и считаете, что он исходит от T*, но не совсем уверены, решения нет. Никакой синтаксис приведения не поможет, поскольку это принципиально проблема дизайна.
Вопрос больше, чем просто использование приведений в стиле 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. Но что, если вы не знаете в этой точке кода, на что на самом деле указывает a? (2) может быть совершенно законным (вы хотите посмотреть только один байт целого числа), но это также может быть ошибкой, и в этом случае ошибка будет приятной, как (3). Операторы приведения C++ предназначены для выявления этих проблем в коде, по возможности предоставляя ошибки времени компиляции или выполнения.
Итак, для строгого «приведения значений» вы можете использовать static_cast. Если вы хотите полиморфное приведение указателей во время выполнения, используйте dynamic_cast. Если вы действительно хотите забыть о типах, вы можете использовать reintrepret_cast. А чтобы просто выбросить const из окна, есть const_cast.
Они просто делают код более явным, чтобы казалось, будто вы знаете, что делаете.
Приведения стиля C легко пропустить в блоке кода. Приведение стилей C++ - это не только лучшая практика; они предлагают гораздо большую гибкость.
reinterpret_cast позволяет преобразовывать целые типы в указатели, однако может быть небезопасным при неправильном использовании.
static_cast предлагает хорошее преобразование для числовых типов, например. от перечислений до целых чисел или целых чисел в числа с плавающей запятой или любых типов данных, в типе которых вы уверены. Он не выполняет никаких проверок времени выполнения.
С другой стороны, dynamic_cast будет выполнять эти проверки, отмечая любые неоднозначные назначения или преобразования. Он работает только с указателями и ссылками и вызывает накладные расходы.
Есть несколько других, но это основные из них, с которыми вы столкнетесь.
См. Эффективный C++ Введение
Я полностью согласен с этим для классов, но имеет ли смысл использование типов C++ для типов POD?
Я думаю так. Все 3 причины применимы к POD, и полезно иметь только одно правило, а не отдельные правила для классов и POD.
Интересно, что в будущем коде для типов POD мне, возможно, придется изменить то, как я выполняю приведение типов.
In short:
static_cast<>()gives you a compile time checking ability, C-Style cast doesn't.static_cast<>()can be spotted easily anywhere inside a C++ source code; in contrast, C_Style cast is harder to spot.- Intentions are conveyed much better using C++ casts.
More Explanation:
The static cast performs conversions between compatible types. It is similar to the C-style cast, but is more restrictive. For example, the C-style cast would allow an integer pointer to point to a char.
char c = 10; // 1 byte int *p = (int*)&c; // 4 bytesSince this results in a 4-byte pointer pointing to 1 byte of allocated memory, writing to this pointer will either cause a run-time error or will overwrite some adjacent memory.
*p = 5; // run-time error: stack corruptionIn contrast to the C-style cast, the static cast will allow the compiler to check that the pointer and pointee data types are compatible, which allows the programmer to catch this incorrect pointer assignment during compilation.
int *q = static_cast<int*>(&c); // compile-time error
Подробнее на:
В чем разница между static_cast <> и кастингом в стиле C
и
Обычное приведение против static_cast против dynamic_cast
Я не согласен с тем, что static_cast<>() более читабелен. Я имею в виду, что это иногда, но в большинстве случаев - особенно для базовых целочисленных типов - это просто ужасно и излишне многословно. Например: это функция, которая меняет местами байты 32-битного слова. Было бы почти невозможно прочитать, используя приведение static_cast<uint##>(), но довольно легко понять, используя приведение (uint##). Изображение кода:imgur.com/NoHbGve
@ToddLehman: Спасибо, но я тоже не сказал always. (но в большинстве случаев да) Конечно, бывают случаи, когда приведение стиля c более читабельно. Это одна из причин, по которой кастинг стиля c все еще жив и работает в C++ imho. :) Кстати, это был очень хороший пример
Код @ToddLehman в этом изображении использует два связанных приведения ((uint32_t)(uint8_t)) для достижения того, что байты, помимо самого низкого, сбрасываются. Для этого есть побитовое и (0xFF &). Использование приведений скрывает намерение.
Возражаю, ваша честь, спросил и ответил.