C++14 для Qt программистов

http://woboq.com/blog/cpp14-in-qt.html
  • Перевод
В этой статье описывается каким образом изменения, принесенные стандартом С++14, отразились или могут отразиться на разработке Qt приложений. Данная статья ориентирована не только на Qt программистов, но также на всех тех, кому интересно развитие С++. Автор оригинала — Olivier Goffart, являющийся одним из разработчиков Qt moc (meta-object compiler).


Обобщенные лямбда-функции


В С++11 были введены лямбда-функции, и Qt5 позволяет использовать их в сигналах. C++14 упрощает использование лямбда-функций, так как теперь тип аргументов может быть выведен автоматически, то есть стало возможным использование auto в качестве типа параметра вместо того, чтобы явно описывать этот тип:
connect(sender, &Sender::valueChanged, [=](const auto &newValue) {
       receiver->updateValue("senderValue", newValue);
 });

Лямбда-функция представляет собой функтор с реализованным оператором operator(). В обобщенных лямбда-функциях этот оператор объявлен как шаблонная функция. Я сделал изменения, которые поддерживают такие функторы и эти изменения были включены в Qt 5.1. C++14 также добавляет возможность захвата не только переменных, но и выражений:
connect(sender, &Sender::valueChanged, [receiver=getReceiver()](const auto &newValue) {
       receiver->updateValue("senderValue", newValue);
 });


Смягчение требований к константным выражениям


В С++11 было введено новое ключевое слово constexpr. В Qt 4.8 был введен новый макрос Q_DECL_CONSTEXPR, который разворачивается в constexpr, если это слово поддерживается компилятором. В Qt 5 этот макрос используется для большого количества функций, где это только представляется возможным.
В С++14 были смягчены требования, предъявляемые к константным выражениям. С++11 позволял использовать constexpr только с единственным оператором возврата и только в функциях-членах с модификатором const. С++14 позволяет намного больше, лишь бы вычисление могло происходить во время компиляции.
/*
Эта функция не скомпилируется в С++11, потому что состоит из нескольких компонентов, содержит цикл и внутреннюю переменную.
Но в С++14 это разрешено
*/ 
constexpr int myFunction(int v) {
  int x = 1;
  while (x < v*v)
    x*=2;
  return x;
}

Функции-члены класса, объявленные как constexpr в С++11 автоматически трактуются как константные, то есть, не изменяющие поля класса. В С++14 неконстантная функция-член класса тоже может быть constexpr. Результатом такого изменения стало то, что функции-члены классов, объявленные constexpr, но не имеющие явно указанного модификатора const, стали неконстантными в С++14, а это означает несовместимость на уровне бинарных файлов. К счастью, в Qt макрос Q_DECL_CONSTEXPR явно объявлял все функции-члены классов константными, поэтому никакого нарушения бинарной совместимости при его использовании нет.
Итак, теперь мы можем вычислять во время компиляции неконстантные функции классов, такие, например, как operator=. Для этого в Qt 5.5 будет введен новый макрос Q_DECL_RELAXED_CONSTEXPR, который будет разворачиваться в constexpr, если компилятор в режиме С++14.

Небольшие изменения в С++14


Стандарт С++14 привнес некоторое количество небольших изменений, цель которых — сделать разработку более удобной. Эти изменения не имеют непосредственного влияния на Qt, но вполне могут быть использованы в Ваших программах, если используется компилятор с поддержкой С++14.

Разделители разрядов чисел

Если нужно определить большую константу в коде, можно использовать апостроф в качестве разделителя разрядов:
int i = 123'456'789;


Двоичные константы

В С++ можно определять десятичные, восьмеричные (начинающиеся с 0), и шестнадцатеричные (начинающиеся с 0x) константы. Теперь появилась возможность определять и двоичные константы, используя префикс 0b:
int i = 0b0001'0000'0001;


Автоматический вывод типа возвращаемого значения

Если у Вас есть встроенная (inline) функция, то Вы можете использовать ключевое слово auto в качестве указания возвращаемого типа, его теперь можно не указывать явно. Компилятор сам его выведет:
// возвращаемый тип будет выведен как 'int'
auto sum(int a, int b) { return a+b; }

Это, к сожалению, не поддерживается для Qt слотов или так называемых invocable методов, так как Qt moc не в состоянии сам определить возвращаемый тип.

Шаблонные переменные

Раньше было возможным сделать шаблонную функцию или класс. Сейчас можно сделать шаблонной и просто переменную.
template<typename T> const T pi = 3.141592653589793;
/*...*/
    float f = pi<float>;
    double d = pi<double>;


Инициализация структур

В С++11 стало возможно инициализировать структуру, у которой нет определенного пользователем конструктора, списком инициализации (значения полей в фигурных скобках), а также появилась возможность присваивать нестатическим полям класса значения по умолчанию прямо в определении класса. Но в С++11 нельзя было использовать сразу оба этих варианта инициализации. В С++14 теперь можно. Этот код будет работать именно так, как и ожидается:
struct MyStruct {
    int x;
    QString str;
    bool flag = false;
    QByteArray str2 = "something";
};
// ...
// не скомпилируется в C++11 потому что MyStruct не POD
MyStruct s = { 12, "1234", true };
Q_ASSERT(s.str2 == "something");


Квалификаторы ссылок для методов классов


Это на самом деле было привнесено не в С++14, а еще в С++11, но мы начали использовать эти квалификаторы только в Qt5, и я не упоминал о них в предыдущих постах, поэтому поговорим о них сейчас.
Рассмотрим следующий код:
QString lower = QString::fromUtf8(data).toLower();

Здесь fromUtf8 возвращает временную переменную. Было бы неплохо, если бы метод toLower использовал уже выделенную память для этой временной переменной и выполнил в ней необходимые преобразования. Именно для подобных случаев и были введены квалификаторы ссылок для функций-членов классов.
Упрощенный код из qstring.h:
class QString {
public:
    /* ... */
    QString toLower() const &
    { /* ... возвращает копию со всеми символами в нижнем регистре ... */ }
    QString toLower() &&
    { /* ... выполняет преобразования в исходной строке ... */ }
    /* ... */
};

Обратите внимание на '&' и '&&' в конце методов toLower. Это квалификаторы ссылок и позволяют перегрузить функцию в зависимости от типа, на который указывает 'this', таким же образом, как квалификатор const позволяет перегрузить метод в зависимости от константности 'this'. В случае, когда toLower вызывается для временной переменной (rvalue) будет выбран второй метод (который с &&) и проведет изменения строки, не копируя ее.
Функции, которые были улучшены с помощью этих квалификаторов в Qt 5.4: QString::toUpper, QString::toLower, QString::toCaseFolded, QString::toLatin1, QString::toLocal8Bit, QString::toUtf8, QByteArray::toUpper, QByteArray::toLower, QImage::convertToFormat, QImage::mirorred, QImage::rgbSwapped, QVersionNumber::normalized, QVersionNumber::segment

Изменения в стандартной библиотеке


С++11 и С++14 добавили много конструкций в стандартную библиотеку, которые во многом перекликаются с имеющимися конструкциями в QtCore. В Qt стандартная библиотека используется очень мало. Мы вообще не хотим, чтобы стандартная библиотека была частью ABI. Это позволит оставаться бинарно совместимыми даже если стандартная библиотека изменится (например libstdc++ и libcpp). Также Qt до сих пор поддерживает некоторые старые платформы, на которых нет стандартной библиотеки С++11. По этим причинам мы ограничиваем использование этой библиотеки.
Но есть исключение — Qt5 объявляет свои алгоритмы устаревшими (deprecated) и сейчас рекомендуется использовать алгоритмы STL (например std::sort вместо qSort).

Заключение


Конечно, может пройти какое-то время, прежде чем Вы сможете использовать новые конструкции С++14 в своих проектах. Но я надеюсь, что вы начнете их применять как и многие другие (Qt Creator, KDE, LLVM). В новых компиляторах MSVC C++14 активен по умолчанию, в clang и gcc нужно использовать специальный флаг (на настоящий момент это -std=c++1y). С помощью qmake можно настроить свой проект на сборку с С++14 начиная с Qt5.4 используя следующую команду: CONFIG += c++14
Поделиться публикацией
Похожие публикации
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Комментарии 20
  • –5
    Неужели Qt наконец перестала изобретать велосипеды? Молодцы
    • +15
      По-моему, Qt'шные велосипеды как-то проще и понятнее.
      • +4
        Я не пытаюсь кого-то уязвить, Qt — лучшая из графических библиотек. Однако еще в самом начале пути они сознательно отказались от использоания шаблонов, потом — стандартных контейнеров, потом POSIX потоков, в конце концов сделали замену для абсолютно всего. Если не ошибаюсь, это первый шаг Qt навстречу стандартному языку и библиотекам.
        • +2
          В самом начале её пути и шаблоны то не всеми компиляторами поддерживались.
          • +1
            Верно, но начали поддерживаться большинством компиляторов еще прежде чем была выпущена первая нормально работающая версия Qt. Так что больше это все-таки похоже на холиварное решение, если бы они тогда не начали с велосипеда, сейчас бы Qt выглядела совершенно по другому.
          • 0
            Извините, а можете напомнить — когда Qt отказалась от шаблонов? Я сам застал только Qt 3 и более поздние, и там шаблоны использовались везде, где это было необходимо и возможно, может я что-то упускаю?
            • +1
              Примерно в 95-96 году они набирали волонтеров для разработки и примерно в то же время была активная дискуссия об использовании шаблонов. Речь шла именно об архитектуре ядра библиотеки и было принято решение от шаблонов отказаться в пользу named object model. Аргументом было как раз то что шаблоны еще плохо поддерживались многими компиляторами. Я до сих пор считаю что это была ошибка.
              • 0
                Учитывая дешевизну памяти в настоящее время возможно Вы и правы. Но тогда я думаю мало кто мог предугадать…
              • +1
                Класс сожержащий Q_OBJECT не может быть шаблоном. Можете проверить это, еще на стадии moс'а вы увидите примерно такое:
                Error: Template classes not supported by Q_OBJECT


                И сделано это не из-за глубокого велосипедизма Qt разработчиков как тут высказывались, а ненужного усложнения генерируемого moc'ом кода. Если посмотрите в moc_файлы, то поймете масштаб. В любом случае Qt явно дали понять, что они выбрали путь динамического полиморфизма, что не хорошо и не плохо, просто «есть».
                • 0
                  ЗЫ: но это, само собою, не значит, что в коде использующем Qt, не может быть шаблонов априори :)
                  • 0
                    И сделано это не из-за глубокого велосипедизма Qt разработчиков как тут высказывались, а ненужного усложнения генерируемого moc'ом кода.

                    Не совсем верно, это сделано из-за того, что современные компиляторы не умеют экспорт символов шаблонных классов и, как следствие, все сгенерированные moc'ом кишки придется вытаскивать в публичные header'ы, что в свою очередь приводит к конфликтам при повышении версии moc'а.
                • +1
                  А никто не уязвлён. Я честно признаюсь, что использую C++ только в рамках разработки на Qt и, соответственно, очень плохо знаю его за этими пределами. Как-то не было нужды.
              • +6
                Есть ещё proof-of-concept Qt без moc, с использованием compile-time reflection из одного из proposal'ов.
                • +1
                  Вот такая штука есть, сами кишки рефлексии то еще мясо, но результат весьма удобен.

                  woboq.com/blog/reflection-in-cpp-and-qt-moc.html

                  Жаль только, что наверняка все навелосипедят свою рефлексию и будет куча своих моделей.
                  • +1
                    Именно про эту статью я и писал выше. По крайней мере compile-time рефлексия будет стандартизирована.
                • 0
                  Я думаю, они их давно не изобретают. Главная причина того, что в Qt так много всякого QList, QSharedPointer, QMutex, qMax в том, что Qt 3.0 зарелизилась в 2001 году и в том, что они не желают взять и задеприкейтить всё, что не умеет C++11 и новую стандартную библиотеку. Может в Qt6 так и будет.

                  А привязывать ли Qt к версии библиотек из какого-нибудь Boost — это религиозный вопрос. Qt тоже как минимум позиционировалась как «единая библиотека, чтобы разрабатывать всё».
                  • +2
                    Использовать Qt в Boost как минимум плохо из-за того, что Boost, в отличие от Qt, постоянно ломает API/ABI, а Qt гарантирует обратную совместимость ABI/API внутри мажорной версии. На данный момент я могу вспомнить только один случай, когда они его все-таки поломали — в Qt 5.2 и только для arm платформы.
                • +1
                  Всегда любил Qt за то, что они знают грань между использованием новых фич и обратной совместимостью!
                  P.S.
                  Квалификаторы ссылок для методов классов

                  А где бы про это почитать? Я отвлекся от развития C++ на немного, и уже упустил новую фичу, которая в умелых руках может быть очень полезной!
                  • +2
                    Например здесь akrzemi1.wordpress.com/2014/06/02/ref-qualifiers/.
                    В целом читать особенно нечего, если вы знаете, что такое cv-qualifier у функции-члена, то и ref-qualifier вы поймете бе запинки. Единственный нюанс — если хоть одна из перегрузок функции имеет ref-qualifier'ы, то и другие перегрузки тоже должны их иметь.
                    • 0
                      В принципе достаточно хорошее пояснение, чтобы не особо сильно не углубляться, можно для себя кратко выделить — «Мысленно подставляем квалификаторы после функции для this, вести себя они будут так же, как перед параметрами функции». Спасибо!

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.