diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23a669a --- /dev/null +++ b/.gitignore @@ -0,0 +1,107 @@ +.build/ +_other/ +_distrib*/ + +# Tmp files +*~ +Thumbs.db* + +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.so.* +*.dll +*.dylib +*.ko +*.obj +*.elf +*.lib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* +*.qm +*.prl + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* + +*_qmlcache.qrc + +# ---> C +# Prerequisites +*.d + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Fortran module files +*.mod +*.smod + diff --git a/ImplicitShared.md b/ImplicitShared.md new file mode 100644 index 0000000..065250f --- /dev/null +++ b/ImplicitShared.md @@ -0,0 +1,254 @@ +![picture](/_resources/images/qtcat.png) + +- [К списку документов](Readme.md) + +# Implicit shared в Qt + +Многие классы C++ в Qt используют неявное совместное использование данных для +максимального использования ресурсов и минимизации копирования. +Неявно совместно используемые классы являются как безопасными, так и эффективными, +когда передаются в качестве аргументов, поскольку передается только указатель на данные, +а данные копируются только тогда, когда функция записывает в них данные, т. е.copy-on-write. + +Implicit sharing в Qt — это механизм, при котором при копировании классов +не происходит копирование данных, а копирование происходит лишь тогда, +когда копии класса потребуется изменить эти данные. + +
+ +Некоторые особенности механизма: + +- При создании общего объекта счётчик ссылок устанавливается в 1. +- Когда новый объект ссылается на общие данные, счётчик ссылок увеличивается. +- Когда объект теряет ссылку на общие данные, счётчик ссылок уменьшается. +- Общие данные удаляются, когда счётчик ссылок становится равен 0. + +Implicit sharing реализован во многих классах C++ в Qt. + +
+ +При разработке таких классов используется паттерн Pimpl. + +
+ +## Pimpl + +Pimpl — Pointer to private implementation. +Это одно из названий паттерна программирования. +Еще его называют чеширским котом — «Cheshire Cat» (это название +мне больше нравится). В чем суть этого паттерна? +Основная идея этого паттерна — это вынести все приватные члены класса и, +в некоторых случаях, функционала в приватный класс. + +
+ +В Qt коде используется подход `d`-указателей. +Смысл в том что объявляется класс `XXXPrivate` и переменная публичного класса +в защищенной секции. В отдельном заголовочном файле или в `.cpp` файле уже +пишется реализация приватного класса. +Иерархия классов идет как по публичным так и по приватным классам. +Для этого объявление приватного класса обычно делается в отдельном `.h` файле, +который называется так-же как публичный, но добавляется +приставка `_p`: `qclassname_p.h`. И эти классы не устанавливаются вместе с +библиотекой, а служат лишь для сборки библиотеки. + +
+ +## Пример класса с использованием Implicit shared + +Пример класса для хранения ошибки, в котором используется Implicit shared. + +- [Заголовочный файл `error_object.h`](examples/error_object.h) + +```C +#pragma once +#ifndef NAYK_ERROR_OBJECT_H +#define NAYK_ERROR_OBJECT_H + +#include +#include +#include +#include + +class ErrorObjectPrivate; // предварительное объявление закрытого класса + +class ErrorObject +{ +public: + ErrorObject(); + ErrorObject(int module, int code, const QString &text, + const QDateTime &dateTime = QDateTime::currentDateTime()); + ErrorObject(const ErrorObject &other); + ErrorObject &operator=(const ErrorObject &other); + ~ErrorObject(); + int module() const; + void setModule(int module); + int code() const; + void setCode(int code); + QString text() const; + void setText(const QString &text); + QDateTime dateTime() const; + void setDateTime(const QDateTime &dateTime); + bool isValid() const; + bool operator==(const ErrorObject &other) const; + bool operator!=(const ErrorObject &other) const; + ErrorObject copy() const; + +private: + QSharedDataPointer d; +} + +Q_DECLARE_METATYPE(ErrorObject) + +#endif // NAYK_ERROR_OBJECT_H +``` + +- [Файл с реализацией `error_object.cpp`](examples/error_object.cpp) содержит код нашего класса `ErrorObject` +и реализацию закрытого класса `ErrorObjectPrivate` + +```C +#include "error_object.h" +#include + +// Закрытый класс ErrorObjectPrivate: + +class ErrorObjectPrivate: public QSharedData +{ +public: + int module {-1}; + int code {-1}; + QString text; + QDateTime dateTime {QDateTime::currentDateTime()}; +}; + +// Класс ErrorObject: + +ErrorObject::ErrorObject() + : d(new ErrorObjectPrivate) +{ + if ( !QMetaType::isRegistered(qMetaTypeId()) ) + qRegisterMetaType(); +} + +ErrorObject::ErrorObject(int module, int code, const QString &text, const QDateTime &dateTime) + : d(new ErrorObjectPrivate) +{ + d->module = module; + d->code = code; + d->text = text; + d->dateTime = dateTime; +} + +ErrorObject::ErrorObject(const ErrorObject &other) = default; + +ErrorObject &ErrorObject::operator=(const ErrorObject &other) = default; + +ErrorObject::~ErrorObject() = default; + +int ErrorObject::module() const +{ + return d->module; +} + +void ErrorObject::setModule(int module) +{ + d->module = module; +} + +int ErrorObject::code() const +{ + return d->code; +} + +void ErrorObject::setCode(int code) +{ + d->code = code; +} + +QString ErrorObject::text() const +{ + return d->text; +} + +void ErrorObject::setText(const QString &text) +{ + d->text = text; +} + +QDateTime ErrorObject::dateTime() const +{ + return d->dateTime; +} + +void ErrorObject::setDateTime(const QDateTime &dateTime) +{ + d->dateTime = dateTime; +} + +bool ErrorObject::isValid() const +{ + return (d->module >= 0) && ((d->code >= 0) || !d->text.isEmpty()); +} + +bool ErrorObject::operator==(const ErrorObject &other) const +{ + return (d->module == other.d->module) && + (d->dateTime == other.d->dateTime) && + ( + ((d->code == other.d->code) && (d->code >= 0)) + || + ((d->code < 0) && (other.d->code < 0) && (d->text == other.text())) + ); +} + +bool ErrorObject::operator!=(const ErrorObject &other) const +{ + return !(*this == other); +} + +ErrorObject ErrorObject::copy() const +{ + return *this; +} +``` + +- Пример использования в коде: + +```C +#include "error_object.h" +//... +ErrorObject error(1, 404, "Resource not found"); +//... какие-то действия +ErrorObject otherError = error; // здесь данные не копируются, увеличивается счетчик ссылок +qDebug() << "Error code:" << otherError.code(); +otherError.setCode(200); // здесь происходит копирование данных, затем изменение в копии +``` + +Для возможности использования типа в метасистеме Qt +добавлено объявление типа в `.h` файле: + +```C +Q_DECLARE_METATYPE(ErrorObject) +``` + +и регистрация типа (должна вызываться один раз до использования в метасистеме): + +```C +qRegisterMetaType(); +``` + +После регистрации тип `ErrorObject` можно использовать для передачи в сигналах и +для конвертации в/из `QVariant`. + +
+ +--- + +
+ +- [К списку документов](Readme.md) + +
+ +
+ diff --git a/QtCodeStyle.md b/QtCodeStyle.md new file mode 100644 index 0000000..ff95db8 --- /dev/null +++ b/QtCodeStyle.md @@ -0,0 +1,577 @@ +![picture](/_resources/images/progcat.png) + +- [К списку документов](Readme.md) + +# Qt Code Style - Правила кодирования с использованием Qt C++ + +Ниже представлен обзор соглашений по написанию кода с использованием Qt. +При использовании Qt предпочтительно использовать стиль кодирования, +который используется в библиотеке, чтобы добиться единообразия кода. + +### Содержание + +- [Кодировка](#кодировка) +- [Заголовочные файлы](#заголовочные-файлы) +- [Директивы препроцессора](#директивы-препроцессора) +- [Отступы](#отступы) +- [Объявление переменных, функций и классов](#объявление-переменных-функций-и-классов) +- [Пробелы](#пробелы) +- [Фигурные скобки](#фигурные-скобки) +- [Круглые скобки](#круглые-скобки) +- [Использование конструкции switch](#использование-конструкции-switch) +- [Разрыв строк](#разрыв-строк) +- [Использование исключений](#использование-исключений) +- [Наследование и ключевое слово virtual](#наследование-и-ключевое-слово-virtual) +- [Неявные преобразования](#неявные-преобразования) +- [Числа с плавающей запятой](#числа-с-плавающей-запятой) +- [Преинкремент и предекремент](#преинкремент-и-предекремент) +- [Другие очевидные вещи](#другие-очевидные-вещи) +- [Общее исключение](#общее-исключение) + +
+ +## Кодировка + +Единственной кодировкой должна быть UTF-8, а использование других следует +приравнять к разжиганию межнациональной розни и карать соответствующей статьёй УК. + +
+ +[наверх](#содержание) + +
+ +## Заголовочные файлы + +При включении заголовочных файлов Qt, всегда используйте следующую форму +записи: + +```C +#include +``` + +Префикс библиотеки (`QtCore`, `QtWidgets` и т.д.) необходим для фреймворков Mac OS X, +а также очень целесообразен для не qmake проектов. + +
+ +Все заголовочные файлы должны быть с защитой от повторного включения посредством `#pragma once` или `#define`. +Применение `#pragma once` вместо `#define` увеличит скорость компиляции во многих случаях +благодаря высокоуровневому механизму; компилятор может самостоятельно сравнивать имена файлов +или inode'ы без необходимости вызова препроцессора для проверки заголовка на наличие `#ifndef` и `#endif`. +Можно использовать обе команды, `#pragma once` и `#define`, для написания переносимого кода, +что также может принести выгоду от применения `#pragma once` при оптимизации (если компилятор её поддерживает): + +```C +#pragma once +#ifndef FOO_BAR_BAZ_H_ +#define FOO_BAR_BAZ_H_ + +//... + +#endif // FOO_BAR_BAZ_H_ +``` + +- Все заголовочные файлы должны быть самодостаточными в плане компиляции. +Пользователи и инструменты разработки не должны зависеть от специальных зависимостей +при использовании заголовочного файла. + +- Подключайте только действительно используемые заголовочные файлы. Не надо заранее +подключать кучу `.h` файлов "на всякий случай". + +- Никогда не пишите реализацию методов класса или функций в заголовочном файле! + +- Вставляйте заголовочные файлы в следующем порядке: парный файл (например, foo.h — foo.cpp), +файлы библиотеки Qt, стандартная библиотека C++, другие библиотеки, файлы вашего проекта. + +- Все заголовочные файлы проекта должны указываться относительно директории исходных файлов проекта +без использования таких псевдонимов как . (текущая директория) или .. (родительская директория). +Директории поиска общих заголовочных файлов можно задать в файле проекта. + +
+ +[наверх](#содержание) + +
+ +## Директивы препроцессора + +Символ решетки всегда в начале строки, а имя директивы с отступом. + +```C +# if defined(QT_NO_KEYWORDS) +# define QT_NO_EMIT +# else +# ifndef QT_NO_SIGNALS_SLOTS_KEYWORDS +# define slots Q_SLOTS +# define signals Q_SIGNALS +# endif +# endif +``` + +
+ +[наверх](#содержание) + +
+ +## Отступы + +- Для обозначения отступа используйте 4 пробела подряд. +- Используйте пробелы, а не табуляцию! + +
+ +[наверх](#содержание) + +
+ +## Объявление переменных, функций и классов + +Один из самых важных постулатов, так как определяет читабельность и общий стиль кода. +Правила объявления переменных собраны в такой список: + +- Объявляйте по одной переменной в строке. +- Избегайте, если это возможно, коротких и запутанных названий переменных +(Например: "a", "rbarr", "nughdeget"). +- Односимвольные имена переменных подходят только для итераторов циклов, +небольшого локального контекста и временных переменных. В остальных случаях +имя переменной должно отражать ее назначение. +- Заводите переменные только по мере необходимости. + +```C +// Неправильно: +int a, b; +char *c, *d; + +// Правильно: +int height; +int width; +char *nameOfThis; +char *nameOfThat; +``` + +- Не используйте глобальные переменные. Вместо них рекомендуется использовать +локальные переменные и передавать их через параметры функций. + +- Функции и переменные должны именоваться со строчной буквы. +Каждое последующие слово в имени переменной должно начинаться с прописной буквы. +- Избегайте аббревиатур. + +```C +// Неправильно +short Cntr; +char ITEM_DELIM = ' '; + +// Правильно +short counter; +char itemDelimiter = ' '; +``` + +- Имена классов всегда начинаются с заглавной буквы. +- Сокращения в camel case (правильно: `QXmlStreamReader`, не правильно: `QXMLStreamReader`). + +
+ +Размещайте свой код в пространстве имён (за некоторыми исключениями). +Пространство имён должно иметь уникальное имя, обычно формируемое на основе названия проекта, +и, возможно, пути. +Не используйте директиву `using` (например, `using namespace foo`). +Не используйте встроенные (`inline`) пространства имён. +Пространства имён делят глобальную область видимости на отдельные именованные области, +позволяя избежать совпадения (коллизий) имён. + +
+ +[наверх](#содержание) + +
+ +## Пробелы + +Пробелы — очень важный элемент форматирования исходного кода. +Он отыгрывает очень большую роль в читабельности кода. + +- Используйте пустые строки для логической группировки операторов, где это возможно. +- Всегда используйте одну пустую строку в качестве разделителя +- Всегда используйте один пробел перед фигурной скобкой + +```C +// Неправильно: +if(foo){ +} + +// Правильно: +if (foo) { +} +``` + +- Всегда ставьте один пробел после `'*'` или `'&'`, если они стоят перед описанием типов. +Но никогда не ставьте пробелы между `'*'` или `'&'` и именем переменной. + +```C +char *x; +const QString &myString; +const char * const y = "hello"; +``` + +- Не окружайте пробелами функции доступа `.` и `->`. +- Бинарные операции отделяются пробелами с двух сторон. +- После преобразования типов не ставьте пробелов. +- Избегайте преобразования типов в стиле C. + +```C +// Неправильно +char* blockOfMemory = (char* ) malloc(data.size()); + +// Правильно +char *blockOfMemory = reinterpret_cast(malloc(data.size())); +``` + +- Не используйте несколько операторов на одной строке +- По возможности, используйте новую строку для тела оператора ветвления: + +```C +// Неправильно: +if (foo) bar(); + +// Правильно: +if (foo) + bar(); +``` + +- Содержимое в пространстве имён пишется без отступа. +- Никогда не добавляйте пробелы в конец строки. + +
+ +[наверх](#содержание) + +
+ +## Фигурные скобки + +Скобки — это вообще отдельная тема. Они, как и пробелы, отыгрывают львиную долю +во внешнем виде и читабельности кода. + +- Возьмите за основу расстановку открывающих фигурных скобок на одной строке +с выражением, которому они предшествуют. +- Исключение: Тело функции и описание класса всегда открывается фигурной скобкой, +стоящей на новой строке. +- Используйте фигурные скобки в условиях, если тело условия в размере превышает одну линию, +или тело условия достаточное сложное и выделение скобками действительно необходимо. +- Исключение 1: Используйте скобки, если родительское выражение состоит +из нескольких строк / оберток. +- Исключение 2: Используйте фигурные скобки, когда тела ветвлений `if-then-else` +занимают несколько строчек. +- Используйте фигурные скобки для обозначения пустого тела условия. + +```C +// Неправильно: +if (codec) +{ + // do something +} + +// Правильно: +if (codec) { + // do something +} + +// Неправильно: +if (address.isEmpty() || !isValid() + || !codec) + return false; + +// Правильно: +if (address.isEmpty() || !isValid() + || !codec) { + return false; +} +``` + +
+ +[наверх](#содержание) + +
+ +## Круглые скобки + +- Используйте круглые скобки для группировки выражений. + +```C +// Неправильно +if (a && b || c) +if (a + b & c) + +// Правильно +if ((a && b) || c) +if ((a + b) & c) +``` + +
+ +[наверх](#содержание) + +
+ +## Использование конструкции switch + +Безусловно, эти условия причина многих дискуссий со стороны разработчиков и создателей +Coding Guidelines — там может быть очень много различных вариантов. +Однако Qt предлагает именно такой вариант: + +- Операторы case должны быть в одном столбце со `switch`. +- Каждый оператор `case` должен иметь закрывающий `break` (или `return`) или комментарий, +которой предполагает намеренное отсутствие `break & return`. + +```C +switch (myEnum) { +case Value1: + doSomething(); + break; +case Value2: +case Value3: + doSomethingElse(); + // fall through +default: + defaultHandling(); + break; +} +``` + +
+ +[наверх](#содержание) + +
+ +## Разрыв строк + +Часто происходит следующее: есть разработчик Вася с большим монитором. +Он пишет себе код спокойно. Потом этот код открывает разработчик Петя, с ноутбуком, +и прозревает — ему приходится много скроллить чтобы прочитать весь код. +Это один из дефектов читабельности. Что же предлагает Qt для спасения? + +- Длина строки кода не должна превышать 100 символов (а лучше 80 - исторический стандарт). +Если надо – используйте разрыв строки. +- Запятые помещаются в конец разорванной линии; +операторы помещаются в начало новой строки. В зависимости от используемой вами IDE, +оператор на конце разорванной строки можно проглядеть. + +```C +// Неправильно: +if (longExpression + + otherLongExpression + + otherOtherLongExpression) { +} + +QMessageBox::information(d->someObjectWithLongName, tr("A long title for mesage"), tr("A very very very very very very long body"), QMessageBox::Ok, QMessageBox::Cancel); + +// Правильно: +if (longExpression + + otherLongExpression + + otherOtherLongExpression) { +} + +QMessageBox::information(d->someObjectWithLongName, + tr("A long title for mesage"), + tr("A very very very very very very long body"), + QMessageBox::Ok, QMessageBox::Cancel); +``` + +
+ +[наверх](#содержание) + +
+ +## Использование исключений + +В Qt исключения (`throw `) не используются. Вместо них применяются коды ошибок. + +
+ +[наверх](#содержание) + +
+ +## Наследование и ключевое слово virtual + +В этом постулате все предельно просто — главное не писать `virtual` +перед названием переопределяемого метода в `.h` файле. + +```C +// Неправильно: +class MyWidget : public QWidget +{ +... +protected: + virtual void keyPressEvent(QKeyEvent *); + +// Правильно: +class MyWidget : public QWidget +{ +... +protected: + void keyPressEvent(QKeyEvent *) override; +``` + +
+ +[наверх](#содержание) + +
+ +## Неявные преобразования + +Не объявляйте неявные преобразования. Используйте ключевое слово `explicit` +для операторов преобразования типа и конструкторов с одним аргументом. +Исключение: конструкторы копирования и перемещения могут объявляться без `explicit`, +т.к. они не выполняют преобразование типов. + +
+ +[наверх](#содержание) + +
+ +## Числа с плавающей запятой + +Числа с плавающей запятой всегда должны быть с десятичной точкой и числами +по обе стороны от неё (даже в случае экспоненциальной нотации). + +```C +// Неправильно: +float f = 1.f; +long double ld = -.5L; +double d = 1248e6; + +// Правильно: +float floatValue = 1.0f; +float floatVariable = 1.0; // OK +double doubleValue = 1248.0e6; +``` + +
+ +[наверх](#содержание) + +
+ +## Преинкремент и предекремент + +Используйте префиксные формы (`++i`) инкремента и декремента; +постфиксную форму используйте только при явной необходимости. +Когда переменная инкрементируется (`++i`, `i++`) или декрементируется +(`--i`, `i--`), а возвращаемое значение не используется, то необходимо +чётко понимать: использовать префиксную форму (`++i`, `--i`) или постфиксную +(`i++`, `i--`). + +
+ +Префиксную форму обычно легче читать, и она часто более эффективна +(как минимум такая же), т.к. не требуется создавать копию значения +до выполнения операции. + +```C +// Неправильно: +for (int i = 0; i < size; i++) { + +// Правильно: +for (int i = 0; i < size; ++i) { +``` + +
+ +[наверх](#содержание) + +
+ +## Другие очевидные вещи + +Еще некоторые правила, которые всегда должны выполняться, их все знают, +но повторим на всякий случай: + +- Не используйте `goto` и другие устаревшие конструкции, +которые нарушают принципы стандарта программирования. +- По возможности не используйте в проектах `qmake`, старайтесь новые проекты +строить с использованием `cmake` актуальной версии. +- Всегда включайте в свои файлы проектов расширренную проверку на ошибки. +Для компилятора `gcc` (`mingw`) это делается добавлением строчки в `CMakeList.txt`: + +```CMake +target_compile_options(${PROJECT_NAME} PRIVATE -Werror -Wall -Wextra -Wpedantic) +``` + +- Также можно выключать устаревшие конструкции языка. Пример для выключения `foreach`: + +```CMake +target_compile_definitions(${PROJECT_NAME} PRIVATE QT_NO_FOREACH) +``` + +- Пользуйтесь системой управления версиями. + +- Пишите по возможности кроссплатформенный код (как минимум поддерживаемый Windows и Linux). Используйте условную компиляцию для кода, специфичного для определенной ОС: + +```C +# if defined (Q_OS_WINDOWS) + // код для Windows +# else + // код для Linux, MacOS +# endif +``` + +- Для указателей (адресов) используйте `nullptr`, это улучшает +безопасность типов. Используйте `'\0'` в качестве символа конца строки +(пустого символа). Это улучшает читабельность кода. + +- Рекомендуется использовать `sizeof(переменная)` вместо `sizeof(тип)`. + +- Не вставляйте бесполезные комментарии в код. +Ваш код должен быть самодокументируемым. Комментарии можно использовать +для отдельных пояснений неочевидных или специфических вещей: + +```C +// Плохо: +int counter; // - счетчик шагов + +// Хорошо: +int stepCounter {0}; +``` + + +
+ +[наверх](#содержание) + +
+ +## Общее исключение + +Этот постулат гласит нам, что нет ничего плохого если вы нарушите какое-то правило, +но только в том случае, если оно делает ваш код уродливым. +Это хороший пример того, что у всех правил есть исключения, и того, +что правила созданы чтобы их нарушать. + +
+ +К сожалению, я не смог найти примера, когда `Qt Coding Guidelines` делают код — уродливым. + +
+ +[наверх](#содержание) + +
+ +--- + +
+ +- [К списку документов](Readme.md) + +
+ +
+ diff --git a/QtCreator.md b/QtCreator.md new file mode 100644 index 0000000..88bf34f --- /dev/null +++ b/QtCreator.md @@ -0,0 +1,61 @@ +![picture](/_resources/images/qtcat.png) + +- [К списку документов](Readme.md) + +# Qt Creator + +## Горячие клавиши для Qt Creator + +Горячие клавиши для Qt Creator, которые значительно упростят жизнь + + +- `Esc` - Выполняет переход к редактированию кода. +Несколько последовательных нажатий этой клавиши переключают пользователя в режим +редактирования, закрывают панели вывода справки, отладки. + +- `F4` - Переключает редактор между файлом реализации (`.сpp`) и соответствующим +заголовочным файлом (`.h`), которые содержат объявления интерфейса и +реализации класса соответственно. + +- `F2` - Выполняет переход к месту объявления переменной, функции, класса, +на имени которых стоял курсор при нажатии. + +- `F1` - Показывает справку для класса или метода Qt, на имени которого стоит курсор. + +- `Ctrl + Shift + R` - Переименование переменной, метода, класса, на имени +которых стоит курсор. Имя будет изменено во всех местах, где встречается +его использование: не только в текущем файле, но и в других файлах проекта. +При замене имени будет учитываться область видимости имени, поэтому замена +произойдёт только в местах обращения к имени. Именно этим это действие отличается +от обычного поиска и замены текста. + +- `Ctrl + Shift + U` - Поиск всех мест обращения к переменной, методу, +классу на имени которого стоит курсор. + +- `Ctrl + K` - Открывает поле быстрого поиска (Locator). + +- `Alt + Enter` - Позволяет открыть доступные дополнительные действия для +переменной, метода, класса, оператора в позиции курсора. +Это дополнительные действия для рефакторинга (реорганизации и улучшения +существующего кода) могут содержать изменение порядка параметров, +изменения в текущем фрагменте кода, добавление фрагментов кода и т.д. + +- `Ctrl + Space` - Вызывает выпадающий список автозавершения кода. + +- `Ctrl + F` - Поиск текста в текущем открытом файле. + +- `Ctrl + Shift + F` - Расширенный поиск текста в файле, проекте или группе +проектов (доступны дополнительные настройки). + +
+ +--- + +
+ +- [К списку документов](Readme.md) + +
+ +
+ diff --git a/QtThreads.md b/QtThreads.md new file mode 100644 index 0000000..d957ed9 --- /dev/null +++ b/QtThreads.md @@ -0,0 +1,253 @@ +![picture](/_resources/images/qtcat.png) + +- [К списку документов](Readme.md) + +# QThread - что нужно знать о потоках + +## Основы + +В Qt любые объекты способные работать с сигналами и слотами являются +наследниками класса `QObject`. +Каждый `QObject` строго привязан к какому-то потоку `QThread` который, +собственно, и занимается обслуживанием слотов и прочих событий данного объекта. +Один поток может обслуживать сразу множество `QObject` или вообще ни одного, +а вот `QObject` всегда имеет родительский поток и он всегда ровно один. +По сути можно считать что каждый `QThread` «владеет» каким-то набором `QObject`. +Внутри каждого `QThread` спрятана очередь сообщений адресованных +к объектам которыми данный `QThread` «владеет». +В модели Qt предполагается что если мы хотим чтобы `QObject` сделал +какое-либо действие, то мы «посылаем» данному `QObject` сообщение `QEvent`. +В этом потоково-безопасном вызове Qt находит `QThread` которому принадлежит +объект receiver, записывает `QEvent` в очередь сообщений этого потока и при +необходимости «будит» этот поток. При этом ожидается что код работающий в +данном `QThread` в какой-то момент после этого прочитает сообщение из +очереди и выполнит соответствующее действие. Чтобы это действительно произошло, +код в `QThread` должен войти в цикл обработки событий `QEventLoop`, +создав соответствующий объект и позвав у него либо метод `exec()`, +либо метод `processEvents()`. Первый вариант входит в бесконечный цикл +обработки сообщений (до получения `QEventLoop` события `quit()` ), +второй ограничивается тем что обрабатывает сообщения ранее накопившиеся в очереди. + +
+ +Итак, как мы уже разобрались, каждый объект в Qt «принадлежит» какому-то потоку. +При этом встает закономерный вопрос: а какому, собственно говоря, именно? +В Qt приняты следующие соглашения: + +- Все «дети» любого «родителя» всегда живут в том же потоке что и +родительский объект. Например попытка сделать `setParent` к объекту живущему +в другом потоке в Qt просто молча фейлится (в консоль пишется предупреждение). + +- Объект у которого при создании не указан родитель живет в потоке +который его создал. + +- При необходимости поток можно менять вызовом `QObject::moveToThread` (переместить объект в поток). +Перемещать можно только верхнеуровневых «родителей» (у которых `parent == null`), +попытка переместить любого «ребенка» будет молча проигнорирована. + +- При перемещении верхнеуровневого «родителя» все его «дети» тоже переедут +в новый поток. + +- Получить «текущий» поток исполнения можно через вызов +функции `QThread::currentThread()`, поток с которым ассоциирован объект — через +вызов `QObject::thread()`. + +- Все GUI-объекты кроме rendering back-end должны жить в GUI-потоке. + +- GUI-потоком является тот в котором был создан объект `QApplication`. + +## Таймеры в потоках + +Таймеры `QTimer` в потоках имеют ряд особенностей, которые нужно учитывать +при проектировании многопоточных приложений на Qt: + +- `QTimer` работает только в потоке с event loop (циклом обработки событий). +Это значит, что если вы хотите использовать `QTimer` в `QThread`, то в этом потоке должен быть запущен `QEventLoop`. +Если нет цикла событий — таймер просто не сработает. + +- QTimer должен быть создан в том потоке, в котором будет работать. +Таймер "привязан" к потоку через QObject::thread(). +Если вы создадите таймер в основном потоке, а потом попытаетесь использовать +его в другом — это будет ошибка. + +- Если объект с таймером перемещается в другой поток через `moveToThread()`, +таймер должен быть создан после перемещения. Иначе он останется привязанным к исходному потоку. + +## Правильная работа с потоками в Qt + +Для правильной работы вашего кода в параллельном потоке используйте следующие правила: + +- Проектируется отдельный класс (worker), потомок QObject, который будет работать в потоке. +- У этого объекта должны быть определены публичные слоты для запуска и остановки +основной работы, например: `void start();` и `void stop();`. +- Также у объекта должны быть определены сигналы для оповещения после начала +и после полного окончания работы, +например `void started();` и `void finished();`. +- Создание внутренних объектов и таймеров должны выполняться только +в слоте `start()` или позже (не в конструкторе). +- Обмен данными с объектом должен осуществляться только через механизм сигнал-слот. +Вызов методов напрямую после помещения объекта в поток запрещен! +- Далее в основном потоке программы создаётся экземпляр объекта worker (без родителя), +создаётся оъект `QThread`, и worker перемещвется в поток через `moveToThread()`. +После этого создаются связи сигнал-слот и запускается поток на выполнение. + +
+ +Пример класса `Worker`. Заголовочный файл `worker.h`: + +```C +#pragma once +#ifndef WORKER_H_ +#define WORKER_H_ +#include +#include + +class Worker: public QObject +{ + Q_OBJECT +public: + explicit Worker(QObject *parent = nullptr); + virtual ~Worker(); +signals: + void started(); + void finished(); + // ... другие сигналы наружу +public slots: + void start(); + void stop(); + // ...другие слоты для наружи +private: + QTimer *m_timer {nullptr}; + // ... +private slots: + void timerTimeOut(); + // ... +}; +#endif // WORKER_H_ +``` + +Файл с реализацией `worker.cpp`: + +```C +#include "worker.h" + +Worker::Worker(QObject *parent) : QObject{parent} +{ + // конструктор выполняется еще в основном потоке! +} + +Worker::~Worker() +{ + // К моменту вызова деструктора поток уже должен быть остановлен! + // но проверить стоит: + stop(); +} + +void Worker::start() +{ + // К этому моменту мы уже в параллельном потоке + + if (m_timer) { // если таймер уже есть, значит это повторный вызов + + return; + } + + m_timer = new QTimer(this); + connect(m_timer, &QTimer::timeout, this, &Worker::timerTimeOut); + // ... другие действия, например настройки и запуск таймера + m_timer->start(1000); + + emit started(); // оповещение, что мы стартанули +} + +void Worker::stop() +{ + // Тут мы еще в параллельном потоке + + if (!m_timer) { // если таймера нет, значит это повторный вызов + + return; + } + + m_timer->stop(); + delete m_timer; + m_timer = nullptr; + + // ... тут остановка всей работы, удаление объектов и т.п. + + emit finished(); // оповещение, что мы остановились +} + +void Worker::timerTimeOut() +{ + // Обработчик таймера срабатывает уже в параллельном потоке + // ... +} + +``` + +Как применить из основного потока, например слот клика по кнопке: + +```C +#include "worker.h" +#include +// ... + +void MainWindow::pushButtonClicked() +{ + Worker *worker = new Worker(); // без родителя + QThread *thread = new QThread(this); + + worker->moveToThread(thread); + + // Объект начнёт работу после запуска потока: + connect(thread, &QThread::started, worker, &Worker::start); + + // Какой-нибудь сигнал для остановки потока: + connect(this, &MainWindow::stopThread, worker, &Worker::stop); + + // Когда объект закончит работу, можно останавливать поток: + connect(worker, &Worker::finished, thread, &QThread::quit); + + // Когда поток завершится, можно удалить объект и класс потока: + connect(thread, &QThread::finished, worker, &Worker::deleteLater); + connect(thread, &QThread::finished, thread, &Worker::deleteLater); + + // ... тут другие нужные сигналы-слоты + + // Запуск потока: + thread->start(); +} + +// когда поток больше не нужен, вызываем emit stopThread(); + +``` + +Обратите внимание, в примере выше все действия по созданию и запуску потока находятся +в одном слоте и после выхода из слота доступны только через сигналы. +Нет защиты от повторного запуска - т.е. сколько раз нажали кнопку, столько потоков и создаётся, +а остановка всех по сигналу `stopThread()`. + +
+ +Для отслеживания количества нужно добавить счетчик запуска или флаг, например. +И при закрытии вашего приложения нужно обязательно дождаться завершения всех +созданных потоков! +Но это уже на вашей совести. + +
+ +*p.s. Код не проверял и писал по памяти. Возможны очепятки.* + +
+ +--- + +
+ +- [К списку документов](Readme.md) + +
+ +
+ diff --git a/README.md b/README.md index 4200859..017269f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ -# Qt_Code_Style +![picture](/_resources/images/qtcat.png) + +# Сборник полезностей по Qt и C++ + +- [Qt Code Style](QtCodeStyle.md) - Правила кодирования с использованием Qt +- [Qt Creator](QtCreator.md) - полезности +- [Implicit Shared в Qt](ImplicitShared.md) - механизм управления копированием данных +- [QThread - что нужно знать о потоках](QtThreads.md) - пример работы с потоками + +
+ +--- + +
-Сборник полезностей Qt \ No newline at end of file diff --git a/_resources/images/progcat.png b/_resources/images/progcat.png new file mode 100644 index 0000000..eca87ce Binary files /dev/null and b/_resources/images/progcat.png differ diff --git a/_resources/images/qtcat.png b/_resources/images/qtcat.png new file mode 100644 index 0000000..721ff2a Binary files /dev/null and b/_resources/images/qtcat.png differ diff --git a/examples/error_object.cpp b/examples/error_object.cpp new file mode 100644 index 0000000..131eb76 --- /dev/null +++ b/examples/error_object.cpp @@ -0,0 +1,138 @@ +/**************************************************************************** +** Copyright (c) 2025 Evgeny Teterin (nayk) +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#include "error_object.h" +#include + +namespace nayk { //============================================================= + +//============================================================================== +class ErrorObjectData : public QSharedData +{ +public: + int module {-1}; + int code {-1}; + QString text; + QDateTime dateTime {QDateTime::currentDateTime()}; +}; +//============================================================================== +void regiserErrorObjectMetaType() +{ + if ( !QMetaType::isRegistered( qMetaTypeId() ) ) { + + qRegisterMetaType(); + } +} +//============================================================================== +ErrorObject::ErrorObject() + : d(new ErrorObjectData) +{ + regiserErrorObjectMetaType(); +} +//============================================================================== +ErrorObject::ErrorObject(int module, int code, const QString &text, const QDateTime &dateTime) + : d(new ErrorObjectData) +{ + regiserErrorObjectMetaType(); + + d->module = module; + d->code = code; + d->text = text; + d->dateTime = dateTime; +} +//============================================================================== +ErrorObject::ErrorObject(const ErrorObject &other) = default; +//============================================================================== +ErrorObject &ErrorObject::operator=(const ErrorObject &other) = default; +//============================================================================== +ErrorObject::~ErrorObject() = default; +//============================================================================== +int ErrorObject::module() const +{ + return d->module; +} +//============================================================================== +void ErrorObject::setModule(int module) +{ + d->module = module; +} +//============================================================================== +int ErrorObject::code() const +{ + return d->code; +} +//============================================================================== +void ErrorObject::setCode(int code) +{ + d->code = code; +} +//============================================================================== +QString ErrorObject::text() const +{ + return d->text; +} +//============================================================================== +void ErrorObject::setText(const QString &text) +{ + d->text = text; +} +//============================================================================== +QDateTime ErrorObject::dateTime() const +{ + return d->dateTime; +} +//============================================================================== +void ErrorObject::setDateTime(const QDateTime &dateTime) +{ + d->dateTime = dateTime; +} +//============================================================================== +bool ErrorObject::isValid() const +{ + return (d->module >= 0) && ((d->code >= 0) || !d->text.isEmpty()); +} +//============================================================================== +bool ErrorObject::operator==(const ErrorObject &other) const +{ + return (d->module == other.d->module) && + (d->dateTime == other.d->dateTime) && + ( + ((d->code == other.d->code) && (d->code >= 0)) + || + ((d->code < 0) && (other.d->code < 0) && (d->text == other.text())) + ); +} +//============================================================================== +bool ErrorObject::operator!=(const ErrorObject &other) const +{ + return !(*this == other); +} +//============================================================================== +ErrorObject ErrorObject::copy() const +{ + return *this; +} +//============================================================================== + +} // namespace nayk //========================================================== diff --git a/examples/error_object.h b/examples/error_object.h new file mode 100644 index 0000000..4c0b398 --- /dev/null +++ b/examples/error_object.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** Copyright (c) 2025 Evgeny Teterin (nayk) +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#pragma once +#ifndef NAYK_ERROR_OBJECT_H +#define NAYK_ERROR_OBJECT_H + +#if defined (LIB_NAYK_CORE) +# define ERROR_OBJECT_EXPORT Q_DECL_EXPORT +#else +# define ERROR_OBJECT_EXPORT Q_DECL_IMPORT +#endif + +#include +#include +#include +#include + +namespace nayk { //============================================================= + +class ErrorObjectData; + +class ERROR_OBJECT_EXPORT ErrorObject +{ +public: + ErrorObject(); + ErrorObject(int module, int code, const QString &text, const QDateTime &dateTime = QDateTime::currentDateTime()); + ErrorObject(const ErrorObject &other); + ErrorObject &operator=(const ErrorObject &other); + ~ErrorObject(); + int module() const; + void setModule(int module); + int code() const; + void setCode(int code); + QString text() const; + void setText(const QString &text); + QDateTime dateTime() const; + void setDateTime(const QDateTime &dateTime); + bool isValid() const; + bool operator==(const ErrorObject &other) const; + bool operator!=(const ErrorObject &other) const; + ErrorObject copy() const; + +private: + QSharedDataPointer d; +}; + +} // namespace nayk //========================================================== + +Q_DECLARE_METATYPE(nayk::ErrorObject) + +#endif // NAYK_ERROR_OBJECT_H