Приведение типов

    Будучи на конференции Qt Developer Days 2010 я узнал, что одним из самых популярных вопросов на собеседовании в разные зарубежные компании, работающие с Qt библиотекой, является вопрос о различиях в способах приведения типов в C++. Поэтому здесь я рассмотрю основные различия между static_cast, dynamic_cast, const_cast, reinterpret_cast, C-style cast, qobject_cast и qvariant_cast



    1. static_cast.

    Синтаксис:
    TYPE static_cast<TYPE> (object);


    static_cast преобразует выражения одного статического типа в объекты и значения другого статического типа. Поддерживается преобразование численных типов, указателей и ссылок по иерархии наследования как вверх, так и вниз. Проверка производится на уровне компиляции, так что в случае ошибки сообщение будет получено в момент сборки приложения или библиотеки.

    2. dynamic_cast

    Синтаксис:
    TYPE& dynamic_cast<TYPE&> (object);
    TYPE* dynamic_cast<TYPE*> (object);


    Используется для динамического приведения типов во время выполнения. В случае неправильного приведения типов для ссылок вызывается исключительная ситуация std::bad_cast, а для указателей будет возвращен 0. Использует систему RTTI (Runtime Type Information). Безопасное приведение типов по иерархии наследования, в том числе для виртуального наследования.

    3. const_cast.

    Синтаксис:
    TYPE const_cast<TYPE> (object);


    Пожалуй самое простое приведение типов. Снимает cv qualifiers — const и volatile, то есть константность и отказ от оптимизации компилятором переменной. Это преобразование проверяется на уровне компиляции и в случае ошибки приведения типов будет выдано сообщение.

    4. reinterpret_cast

    Синтаксис:
    TYPE reinterpret_cast<TYPE> (object);


    Приведение типов без проверки. reinterpret_cast — непосредственное указание компилятору. Применяется только в случае полной уверенности программиста в собственных действиях. Не снимает константность и volatile. применяется для приведения указателя к указателю, указателя к целому и наоборот.

    5. C-style cast

    Синтаксис:
    TYPE (TYPE*) object;


    Си-шный метод приведения типов. Пожалуй самый нежелательный способ приведения типов. Страуструп пишет:
    «Например, что это значит выражение — x = (T)y;. Мы не знаем. Это зависит от типа T, типов x и y. T может быть названием типа, typedef или может быть параметр template-а. Может быть, х и у являются скалярными переменными и Т представляет собой значение преобразования. Может быть, х объекта класса, производного от класса Y и Т — нисходящее преобразование. По этой причине программист может не знать, что он делает на самом деле.»
    Вторая причина нежелательного использования приведения типов в C-style — трудоемкость процесса поиска мест приведения типов.

    6. qobject_cast

    Синтаксис:
    TYPE qobject_cast<TYPE>(QObject *object)


    Приводит объект QObject* к типу TYPE если объект типа объекта TYPE или тип наследует от TYPE иначе возвращает 0. qobject_cast от 0 также дает 0. Необходимое условие. Класс должен наследовать от QObject и содержать в себе макрос Q_OBJECT. Функция ведет себя аналогично стандартному dynamic_cast, но при этом не использует RTTI. Вот как описана данная функция в Qt 4.7.0:

    1. template <class T>
    2. inline T qobject_cast(QObject *object)
    3. {
    4. #if !defined(QT_NO_MEMBER_TEMPLATES) && !defined(QT_NO_QOBJECT_CHECK)
    5.   reinterpret_cast<T>(0)->qt_check_for_QOBJECT_macro(*reinterpret_cast<T>(object));
    6. #endif
    7.   return static_cast<T>(reinterpret_cast<T>(0)->staticMetaObject.cast(object));
    8. }
    * This source code was highlighted with Source Code Highlighter.


    Итак, что тут происходит:

    Во-первых если не определены QT_NO_MEMBER_TEMPLATES (определяется только в том случае, если используется версия Microsoft Visual Studio ниже 2002) и QT_NO_QOBJECT_CHECK (определяется в случае использования версии Microsoft Visual Studio ниже 2003), то происходит проверка наличия макроса Q_OBJECT в объявлении класса. И после этого выполняется непосредственно само преобразование — сначала получаем статический объект класса QMetaObject, который называется staticMetaObject, у которого вызывается метод cast, который возвращает const_cast переданного ему объекта, попутно проверяя наследуется ли данный объект от QObject. Далее полученному объекту делается static_cast и возвращается результат.

    7. qvariant_cast

    Синтаксис:
    TYPE qvariant_cast<TYPE>(const QVariant &value)


    Приводит объект класса QVariant к нужному классу. Функция аналогична функции qVariantValue.

    Рассмотрим, что происходит внутри:

    1. template<typename T> inline T qvariant_cast(const QVariant &v)
    2. {
    3.   const int vid = qMetaTypeId<T>(static_cast<T *>(0));
    4.   if (vid == v.userType())
    5.     return *reinterpret_cast<const T *>(v.constData());
    6.   if (vid < int(QMetaType::User)) {
    7.     T t;
    8.     if (qvariant_cast_helper(v, QVariant::Type(vid), &t))
    9.       return t;
    10.   }
    11.   return T();
    12. }
    * This source code was highlighted with Source Code Highlighter.


    В первой секции кода производится получение идентификатора класса через метасистему Qt. В том случае если класс не зарегистрирован через Q_DECLARE_METATYPE, компиляция кода с приведением к этому типу выдаст ошибку. Далее, если тип объекта, полученный от метасистемы совпадает с типом в значении QVariant, производится reinterpret_cast содержимого объекта, если идентификатор класса не является встроенным типом и его id не совпадает с заложенным в значении QVariant, то возвращается TYPE(). Для случаев, когда мы приводим к встроенному типу, вызывается функция qvariant_cast_helper, которая вызывает в свою очередь функцию convert, адрес которой хранится в структуре Handler. В ней уже осуществляется приведение способом подходящим для типа TYPE. Если конвертация не удалась возвращается объект TYPE()

    UPD: C-style cast по сути самое медленное преобразование, так как в этом случае последовательно перебираются следующие вызовы:
    • const_cast
    • static_cast
    • static_cast + const_cast
    • reinterpret_cast
    • reinterpret_cast + const_cast


    UPD: Спасибо BaJlepa:
    1. const_cast также умеет добавлять cv-квалификаторы
    2. для преобразования указателей лучше использовать двойной static_cast через void* вместо reinterpret_cast, потому как такое преобразование позволяет быть уверенным в том, что только pointer-ы участвуют в приведении

    Источники:
    а) http://alenacpp.blogspot.com/2005/08/c.html
    б) http://www.cppreference.com
    в) http://www.cplusplus.com/doc/tutorial/typecasting/
    г) http://doc.qt.nokia.com/4.7/qobject.html#qobject_cast
    д) http://www2.research.att.com/~bs/bs_faq2.html
    е) http://doc.qt.nokia.com/4.7/qvariant.html#qvariant_cast
    UPD: ж)http://www.rsdn.ru/Forum/Info/FAQ.cpp.c-stylecast.aspx
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 59

      +2
      >2. dynamic_cast
      >В случае неправильного приведения типов вызывается исключительная ситуация std::bad_cast и будет возвращен NULL

      Как-то интересно у вас получилось, если исключение кинулось, то куда NULL тогда вернулся?
        +1
        Да, справедливо, bad_cast, вызывается только при работе со ссылками. При работе с указателями возвращается NULL
          0
          В С++ используется не NULL, а 0
            0
            да, поправил)
              +1
              #ifdef __cplusplus
              #define NULL 0
              © stddef.h

              NULL тоже используется, просто задефайнен как 0. Использую для указателей именно NULL — так наглядней, мне кажется.
                –1
                на некоторых платформах NULL это -1

                а -1 это всегда число, а 0 тип контекстнозависим
                  0
                  мне кажется это FALSE на некоторых платформах (базы данных, например, может не все) равен -1, а не NULL. Можно пример, чтоб я понял, что я ошибаюсь?
                    0
                    не FALSE, а TRUE, конечно.
                      –1
                      в С нет boolean'а. Для if, while, и так далее используются целочисленный типы: нуль/не нуль
                      в С++ все же приводится к типу boolean:
                      * целочисленный приводятся нуль/не нуль
                      * указатели: валидный/невалидный

                      Опять же в С указатели — это фактически целочисленные типы (хотя это очень приблеженно) — но в любом случае они приводятся к целому. В С++ они к целому не приводятся, а только к boolean'у

                      Не надо путать физическое значение, и логическое, которое в С++ куда более важно

            0
            Всегда интересовал вопрос, чем при использовании C++ отличается reinterpret_cast и C-style cast?
              0
              по сути ничем
                +1
                Вот здесь www.rsdn.ru/forum/cpp/3812985.aspx почитайте
                  0
                  Спасибо, очень обстоятельный тред :-)
                  +1
                  reinterpret_cast, например не снимает константность
                    +1
                    Ну и по коду их проще грепать
                    +3
                    Например, при касте к одному из предков, участвовавших в множественном наследовании, reinterpret_cast не сдвинет указатель (и он станет невалидным), а си-стайл каст сдвинет.
                      +2
                      Такой каст может применяться, чтобы обойти ограничения private/protected наследования, есличо.
                      +1
                      обновил статью — насчет порядка вызова cast-ов при C-Style cast
                      +11
                      ой-ой.

                      вовремя я ушёл в javascript с плюсов :)

                      язык должен помогать решать задачу, а не ломать себе мозг
                        +1
                        Зря вы так… Адепты объектно-ориентированного проектирования, включая меня, вас сотрут в порошок :)

                        ЗЫ Поставил плюсик вашему комментарию, но это, судя по всему вас все равно не спасет :)
                          +4
                          Обнаружена холивар-вероятная точка. Каждый язык хорош для своей области.
                            +7
                            ООП != C++ Можно быть адептом ООП и не ломать себе мозг выбором способа приведения типов
                              +1
                              динамическая типизация != статическая. При статической «ломать мозг» придется в любом ЯП, просто синтаксис операторов приведения типов будет другим.
                            0
                            Ну не должен, но все равно со временем привыкаешь. И узнаешь много нового. Изучать несовершенные технологии — полезно.
                            • UFO just landed and posted this here
                                0
                                Для познания «как оно внутри». От всего не абстрагируешся, все абстракции, как говорят, текут. И потому даже высокоуровнему программисту необходимо знать низкоуровневые подробности (использовать их или нет вопрос другой).
                                • UFO just landed and posted this here
                                    0
                                    Что-то побоку, что-то нет, но в чем-то вы конечно правы.
                                    • UFO just landed and posted this here
                                        0
                                        Согласен. Но обычно приходиться сталкиваться со многими языками, и там ситуация может быть другой. В конечном итоге все переплетается в некотрой степени :)
                              +1
                              Не все задачи можно на javascript-е решить)
                                +1
                                Если для вас это ломка мозга, то зачем вы вообще в программисты пошли? Вы не знаете чем отличается статическая от динамической типизации?
                                  +1
                                  Друзья, я и не думал холиварить :)

                                  Это была всего лишь внезапная эмоция, которой я счёл уместным поделиться, скорее даже в качестве иронии. Так сложилось, что за c++ мне нынче остаётся только наблюдать. А может даже в тайне ото всех я мечтаю когда-нибудь вернуться ;)
                                  +1
                                  пацаны, зацените мой юмор:
                                  #define c_dynamic_cast(__struct,__field,__ptr) ((__struct*)&(((char*)__ptr)[(char*)0 - (char*)&((__struct*)0)->__field]))
                                  
                                    0
                                    dynamic_cast — это не такая тривиальная функция. Ей для работы необходимо, грубо говоря, по дереву RTTI проходить. В compile-time это не решается.
                                      0
                                      поэтому отнес к юмору, поскольку в Си (речь про Си без плюсов, иначе как бы зачем макрос?), работы с наследованием структур нет. Макрос может пригодится, когда из указателя на поле нужно получить указатель на заголовок структуры. Не хотел никак покушаться на нетривиальность Си++ ;)
                                        0
                                        А почему вы её назвали «dynamic_cast»? В С++ dynamic_cast делает совсем другое.

                                        Чтобы запутать того, кто потом код читать будет?
                                          0
                                          Во первых не «dynamic_cast», а «c_dynamic_cast», как бы намекая. Во вторых, если например в структуре используется вложение:

                                          struct child {
                                          struct parent head; // Как это в том же конкуренте топика — gobject
                                          struct field1 field1;

                                          struct fieldN fieldN;
                                          };

                                          то приведение типов:

                                          struct parent* p = getSome();
                                          struct child* c = (struct child*)p;

                                          будет менее предпочтительней, чем

                                          struct parent* p = getSome();
                                          struct child* c = c_dynamic_cast(struct child, head, p);

                                          поскольку перед head может быть встрена, скажем, структура отладки, или структура выравнивания. Вот чем руководствовался:
                                          — «dynamic» — динамическое;
                                          — «cast» — приведение типа.
                                          В них не скрывается сакрального смысла. Во всяком случае угловые скобки все расставят по местам.
                                            +1
                                            Мне кажется, вы offsetof (стандартный макрос из stdlib) с dynamic_cast'ом путаете.
                                              +1
                                              #define offsetof(type, field) ((size_t)(&((type *)0)->field))

                                              этот? Его использование менее удобно для указанной выше специфики операции.
                                    +3
                                    Я давно уже програмлю на плюсах, и давно уже програмлю только на С( так как часть работы сидела я линукс кернел).
                                    И на самом деле уже два года это уже не моя работа.
                                    Но я никогда не применял эти касты и не понимал зачем они нужны, и зачем их придумали — тоже не принимал.
                                    И за 10( а то и больше) лет, у меня никогда не было проблем с кастами, приведением типов и виртуальными методами.
                                    Других багов было много, и магия была и не шаманства.
                                    Но старый добрый CBase base=(CBase)сhild; меня никогда не подводил
                                      +1
                                      >Но старый добрый CBase base=(CBase)сhild; меня никогда не подводил

                                      Разве это не будет сделано автоматически без ручного приведения типов? Приведение типов сверху вниз очень полезно при разработке gui приложений, впрочем любовь к ним говорит о недостатках дизайна.
                                        0
                                        Это я образно. Вообще приведение будет произведено при добавлении в какой либо контейнер.
                                        Просто потому что любой контейнер — что монстров на уровне, что элементов этого ГУЯ( который есть одно из мест где возможны самые интересные применения ООП и приведения разностных типов к единому «работельному» состоянию) — так вот этот контейнер строго типизирован( не строго типизироавный контейнер на самом деле тоже строго типизирован. но его тип не строго следит за тем что в него внутрях и позволяет различные вольности, так что к этой фразе особо не придерайтесь)
                                      0
                                      Вопрос по существу, насколько оправдано юзание dynamic_cast'ов в Qt. И еще есть, как минимум, еще два вида кастов
                                      qdbus_cast,qstyleoption_cast
                                        0
                                        Использование dynamic_cast-ов в большинстве своем не оправдано. Во-первых сами тролли, если посмотреть по коду, не используют dynamic_cast и RTTI, во-вторых функции RTTI в основном берет на себя Meta System.

                                        Есть еще qgraphicsitem_cast и qscriptvalue_cast, я просто рассмотрел два наиболее мною используемых
                                          0
                                          хорошо, а как мне лучше тогда такую проблему обойти?
                                          Есть некий абстрактный класс
                                          class ChatViewController
                                          Но его реализация должна представлять собой QWebPage, в итоге получается
                                          class ChatStyleOutput : public QWebPage, public ChatViewController
                                          Таким образом, контроллер невозможно унаследовать от QObject'а, не говоря даже о том, что возможен случай, в котором контроллер просто не будет вообще никаким местом связан с QObject'ом.
                                          В итоге я не могу пользоваться qobject_cast'ами. А без множественного наследования я получаю весьма сложный код.
                                            0
                                            А зачем вообще в этом случае кастовать «вниз» — от интерфейса к реализации?
                                              0
                                              Есть контроллер, а есть view для него, фактически нам нужно менять контроллер для view'а. Тот, кто устанавливает контроллера, не должен знать, что за view юзается, а view должен знать что ему за контроллера подсунули. Тут приведение нужно без вариантов.
                                              0
                                              Нет, ну это всего лишь рекомендация. Следовать ей или нет решать, собственно, программисту, который использует их библиотеку. Если использование dynamic_cast-а необходимо, то его надо использовать.

                                              Такая же ситуация, например, с применением обработки исключений. Трольтех рекомендует проверять возвращаемое значение, но если основная часть кода написана с применением обработки, то имеет смысл не нарушать общий стиль процесса разработки проекта.
                                                0
                                                Меня больше интересуют возможности обхода этой проблемы Qtшными средствами, чтобы прога работала при выключенном RTTI
                                                  0
                                                  а почему в данном случае нельзя использовать static_cast?
                                                    0
                                                    А если классы будут в разных библиотеках?
                                                      0
                                                      Не принципиально. Если ChatStyleOutput будет лежать в другой библиотеке, то при объявлении класса подключается header-файл. А значит при сборке библиотеки static_cast должен отработать нормально.
                                                        0
                                                        Проблему короче решил через Q_INTERFACES путем небольшой переделки API
                                                  0
                                                  у меня есть интересные разработки, который использует исключения. так вот там dynamic cast очень много встречается. но так как это использует цикл обработки сообщений — то это не медленно
                                            0
                                            спасибо, на си++ не пишу, но всегда было интересно, что все это значит
                                            добавлю статью в избранное
                                              +2
                                              Хочу немного дополнить:

                                              1. const_cast также умеет добавлять cv-квалификаторы
                                              2. для преобразования указателей лучше использовать двойной static_cast через void* вместо reinterpret_cast
                                              или вот такой pointer_cast

                                              /// This template functions should be used when we perform cast from one pointer type to another
                                              /// It's safer than using reiterpret_cast
                                              ///
                                              /// It doesn't allow to do such things like:
                                              /// int i = 10;
                                              /// A *a = pointer_cast<A*>(i);
                                              /// Only pointer could be used in this function.

                                              template<typename result, typename source>
                                              result pointer_cast(source *v)
                                              {
                                                return static_cast<result>(static_cast<void*>(v));
                                              }

                                              template<typename result, typename source>
                                              result pointer_cast(const source *v)
                                              {
                                                return static_cast<result>(static_cast<const void*>(v));
                                              }

                                                0
                                                спасибо. насчет добавления cv-квалификаторов знал, забыл упомянуть, а про преобразование через void* — отличное пояснение:
                                                > /// It's safer than using reiterpret_cast
                                                >///
                                                >/// It doesn't allow to do such things like:
                                                >/// int i = 10;
                                                >/// A *a = pointer_cast<A*>(i);
                                                >/// Only pointer could be used in this function.
                                                +1
                                                static_cast так же умеет работать со ссылками и указателями, и с любыми типами, содержащими виртуальные члены, например, не обязательно статический тип.
                                                pastebin.org/251996
                                                Разница в работе.

                                                Only users with full accounts can post comments. Log in, please.