FAQ по преобразованию типов в C ++ - PullRequest
12 голосов
/ 26 февраля 2011

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

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

Ответы [ 3 ]

21 голосов
/ 28 февраля 2011

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

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

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

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

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

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

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

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

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

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

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

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

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

Поскольку C ++ является языком OO, существует ряд приведений, при которых отношение между Base и Derived имеет значение.,Здесь очень важно понимать разницу между реальными объектами, указателями и ссылками (особенно, если вы пришли из .Net или Java).Во-первых, реальные объекты.Они имеют только один тип, и вы можете преобразовать их в любой базовый тип (на данный момент игнорируя частные базовые классы).Преобразование создает объект new базового типа.Мы называем это «нарезка»;производные части нарезаны.

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

Конверсия другим способом сложнее.В общем, не каждый Base* будет указывать на базовый подобъект внутри производного объекта.Базовые объекты могут также существовать в других местах.Следовательно, возможно, что преобразование не получится.C ++ дает вам два варианта здесь.Либо вы сообщаете компилятору, что уверены, что указываете на подобъект внутри Derived с помощью static_cast<Derived*>(baseptr), либо просите компилятор проверить с помощью dynamic_cast<Derived*>(baseptr).В последнем случае результат будет nullptr, если 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, то это не будет учитываться в последовательностях неявного преобразования.

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

3 голосов
/ 26 февраля 2011

Не знаю ни одного, так что давайте посмотрим, нельзя ли это сделать здесь ... надеюсь, я правильно понял.

Прежде всего, неявный / явный:

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

Акция:

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

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

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

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

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

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

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

1 голос
/ 26 февраля 2011

Для целочисленных типов, проверьте книгу Безопасное кодирование C и C ++ от Seacord, главу о целочисленных переполнениях.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...