Подтвердить что ты не робот

Преобразование типа С++ FAQ

Где я могу найти превосходно понятную статью о преобразовании типа С++, охватывающую все ее типы (продвижение, неявное/явное и т.д.)?

Я изучаю С++ в течение некоторого времени, и, например, механизм виртуальных функций кажется мне более ясным, чем эта тема. Я считаю, что это связано с тем, что авторы учебников слишком усложняют (см. Книгу Stroustroup и т.д.).

4b9b3361

Ответ 1

(Оказание сумасшедшего Эдди для первого ответа, но я чувствую, что его можно сделать более ясным)

Преобразование типов

Почему это происходит?

Преобразование типов может происходить по двум основным причинам. Один из них заключается в том, что вы написали явное выражение, например static_cast<int>(3.5). Другая причина заключается в том, что вы использовали выражение в месте, где компилятору нужен другой тип, поэтому он будет вставлять вам конверсию. Например. 2.5 + 1 приведет к неявному преобразованию из 1 (целое число) в 1.0 (двойной).

Явные формы

Существует только ограниченное число явных форм. Во-первых, С++ имеет 4 именованные версии: static_cast, dynamic_cast, reinterpret_cast и const_cast. С++ также поддерживает листинг C-стиля (Type) Expression. Наконец, есть листинг

4 названных формы документируются в любом хорошем вводном тексте. Листинг C-стиля расширяется до static_cast, const_cast или reinterpret_cast, а литье "стиль конструктора" является сокращением для static_cast<Type>. Однако из-за проблем с синтаксическим анализом для "типа конструктора" требуется отдельный идентификатор имени типа; unsigned int(-5) или const float(5) не являются законными.

Неявные формы

Намного сложнее перечислить все контексты, в которых может произойти неявное преобразование. Так как С++ - это тип OO-языка, существует много ситуаций, в которых у вас есть объект A в контексте, где вам нужен тип B. Примеры - это встроенные операторы, вызов функции или перехват исключения по значению.

Последовательность преобразования

Во всех случаях, implict и explicit, компилятор попытается найти последовательность преобразования. Это серия шагов, которые вы получаете от типа A до типа B. Однако точная последовательность преобразования зависит от типа трансляции. A dynamic_cast используется для выполнения проверенного преобразования Base-to-Derived, поэтому шаги должны проверять, наследует ли Derived от Base, через какой промежуточный класс (ы). const_cast может удалить как const, так и volatile. В случае a static_cast возможные шаги являются наиболее сложными. Он будет преобразовывать между встроенными арифметическими типами; он преобразует базовые указатели в производные указатели и наоборот, он рассмотрит конструкторы классов (типа назначения) и операторы литья классов (типа источника), и он добавит const и volatile. Очевидно, что некоторые из этих шагов являются ортогональными: арифметический тип никогда не является указателем или типом класса. Кроме того, компилятор будет использовать каждый шаг только один раз.

Как мы отмечали ранее, некоторые последовательности преобразования являются явными, а другие неявны. Это имеет значение static_cast, поскольку использует пользовательские функции. Некоторые из этих преобразований могут быть отмечены как explicit (В С++ 03 могут использоваться только конструкторы). Компилятор пропускает (без ошибок) любую функцию преобразования explicit для неявных преобразований. Конечно, если альтернатив нет, компилятор все равно даст ошибку.

Арифметические преобразования

Целочисленные типы, такие как char и short, могут быть преобразованы в "более крупные" типы, такие как int и long, а более мелкие типы с плавающей точкой аналогичным образом могут быть преобразованы в более крупные типы. Подписанные и неподписанные целые типы могут быть преобразованы друг в друга. Целочисленные и с плавающей точкой могут быть изменены друг на друга.

Базовые и производные преобразования

Так как С++ является языком OO, существует ряд отливок, в которых важна связь между базой и производными. Здесь очень важно понять разницу между фактическими объектами, указателями и ссылками (особенно если вы используете .NET или Java). Во-первых, фактические объекты. У них есть только один тип, и вы можете преобразовать их в любой базовый тип (игнорируя частные базовые классы на данный момент). Преобразование создает новый объект базового типа. Мы называем это "нарезкой"; производные части отрезаны.

Другой тип преобразования существует, когда у вас есть указатели на объекты. Вы всегда можете преобразовать Derived* в Base*, потому что внутри каждого производного объекта существует подобъект Base. С++ автоматически применит правильное смещение Base with Derived к вашему указателю. Это преобразование даст вам новый указатель, но не новый объект. Новый указатель укажет на существующий под-объект. Следовательно, литой никогда не будет срезать производную часть вашего объекта.

Преобразование в другую сторону сложнее. В общем случае не каждый Base* указывает на базовый под-объект внутри производного объекта. Базовые объекты могут также существовать в других местах. Следовательно, возможно, что преобразование не удастся. С++ дает вам два варианта. Либо вы сообщите компилятору, что вы уверены, что указываете на подобъект внутри Derived через static_cast<Derived*>(baseptr), или попросите компилятор проверить с помощью dynamic_cast<Derived*>(baseptr). В последнем случае результат будет NULL, если baseptr фактически не указывает на производный объект.

Для ссылок на Base и Derived применяется то же самое, за исключением dynamic_cast<Derived&>(baseref): он будет бросать std::bad_cast вместо возврата нулевого указателя. (Нет таких вещей, как нулевые ссылки).

Пользовательские преобразования

Существует два способа определения пользовательских преобразований: через тип источника и тип адресата. Первый способ заключается в определении члена operator DestinatonType() const в типе источника. Обратите внимание, что он не имеет явного типа возврата (он всегда DestinatonType) и что он const. Конверсии никогда не должны изменять исходный объект. Класс может определять несколько типов, к которым он может быть преобразован, просто добавляя несколько операторов.

Второй тип преобразования через тип адресата зависит от пользовательских конструкторов. Конструктор T::T, который можно вызвать с одним аргументом типа U, может быть использован для преобразования объекта U в объект T. Не имеет значения, имеет ли этот конструктор дополнительные аргументы по умолчанию, и не имеет значения, передается ли аргумент U по значению или по ссылке. Однако, как отмечалось ранее, если T::T(U) является explicit, то оно не будет рассматриваться в неявных последовательностях преобразования.

возможно, что возможны несколько последовательностей преобразования между двумя типами в результате пользовательских последовательностей преобразования. Поскольку это по существу вызовы функций (для определяемых пользователем операторов или конструкторов), последовательность преобразования выбирается с помощью разрешения перегрузки различных вызовов функций.

Ответ 2

Не знаю ни одного, поэтому давайте посмотрим, не может ли он быть здесь... надеюсь, что все правильно.

Во-первых, неявный/явный:

Явное "преобразование" происходит везде, где вы делаете актерский состав. Более конкретно, static_cast. Другие ролики либо не делают никакого преобразования, либо охватывают разные диапазоны тем/конверсий. Неявное преобразование происходит везде, где происходит преобразование без вашего конкретного слова (без кастингов). Считайте это следующим образом: использование броска явно указывает ваше намерение.

Promotion:

Продвижение происходит, когда у вас есть два или более типов, взаимодействующих в выражении разного размера. Это особый случай типа "принуждение", через который я перейду через секунду. Продвижение просто берет малый тип и расширяет его до более крупного типа. Нет стандартного набора размеров для числовых типов, но, вообще говоря, char < короткое < int < длинный < длинный длинный и плавающий < двойной < длинный двойной.

Принуждение:

Принуждение случается, когда любые типы времени в выражении не совпадают. Компилятор будет "принуждать" меньший тип к большему типу. В некоторых случаях, таких как преобразование целого числа в double или unsigned type в подписанный тип, информация может быть потеряна. Принуждение включает продвижение, поэтому аналогичные типы разного размера решаются таким образом. Если продвижение недостаточно, интегральные типы преобразуются в плавающие типы, а неподписанные типы преобразуются в подписанные типы. Это происходит до тех пор, пока все компоненты выражения не будут иметь один и тот же тип.

Эти действия компилятора происходят только в отношении сырых, числовых типов. Принуждение и продвижение не выполняются с определенными пользователем классами. Вообще говоря, явное кастинг не имеет реального значения, если вы не измените правила продвижения/принуждения. Однако он избавится от предупреждений компилятора, которые часто вызывает принуждение.

Пользовательские типы могут быть преобразованы. Это происходит во время разрешения перегрузки. Компилятор найдет различные объекты, которые напоминают имя, которое вы используете, а затем выполните процесс, чтобы разрешить, какой из объектов следует использовать. Предпочтительно преобразование "идентичность"; это означает, что a f(t) разрешит f(typeof_t) что-нибудь еще (см. Функция с типом параметра, которая имеет экземпляр-конструктор с не-const ref, выбранным? для некоторые путаницы, которые могут генерировать). Если преобразование идентичности не работает, система переходит через эту сложную высшую иерархию попыток конверсии, которая включает (надеюсь, в правильном порядке) преобразование в базовый тип (разрезание), определяемые пользователем конструкторы, пользовательские функции преобразования. Там какой-то напуганный язык о ссылках, которые, как правило, не важны для вас, и что я не совсем понимаю, не глядя в любом случае.

В случае преобразования типа пользователя явное преобразование имеет огромное значение. Пользователь, который определил тип, может объявить конструктор как "явный". Это означает, что этот конструктор никогда не будет рассмотрен в таком процессе, как я описал выше. Чтобы вызвать объект таким образом, чтобы использовать этот конструктор, вы должны явно сделать это путем кастинга (обратите внимание, что синтаксис, такой как std::string("hello"), не является, строго говоря, вызовом конструктора, а вместо этого "функциональным стилем", литые).

Поскольку компилятор будет молча смотреть конструкторы и перегружать переходы преобразования во время разрешения имен, настоятельно рекомендуется объявить первое как "явное" и не создавать последнее. Это связано с тем, что в любое время, когда компилятор молча делает что-то там для ошибок. Люди не могут помнить каждую деталь обо всем дереве кода, даже в том, что в настоящее время находится в сфере охвата (особенно добавление в koenig lookup), поэтому они могут легко забыть о некоторых деталях, из-за которых их код делает что-то непреднамеренное из-за конверсий. Требование явного языка для конверсий делает такие несчастные случаи намного сложнее сделать.

Ответ 3

Для целых типов проверьте книгу Secure Coding n C и С++ Seacord, главу о переполнении целых чисел.

Что касается неявных преобразований типов, вы найдете книги Effective С++ и More Effective С++ очень и очень полезными.

На самом деле, вы не должны быть разработчиком С++, не читая их.