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