Современный C++ (11/14/17/20…) настойчиво учит нас: «Забудьте про new и delete, используйте умные указатели». Это отличный совет для чистого C++, но как только вы открываете документацию Qt, на вас снова прыгают T*.

Почему даже в 2026 году невозможно написать серьезное приложение на Qt, используя исключительно умные указатели? Давайте разберемся, где «умный» код ломает логику фреймворка.

1. Конфликт систем владения: иерархия QObject vs Smart Pointers

Фундаментальная особенность Qt — автоматическое управление памятью через иерархию объектов. Передавая указатель на родителя в конструктор QObject (или его наследников, например, QWidget), вы делегируете управление временем жизни объекта этому родителю.

Проблема двойного удаления (Double Free) Ошибка возникает, когда QObject, имеющий родителя, помещается в умный указатель (например, std::unique_ptr или QScopedPointer). В этом случае возникают два независимых владельца:

  1. Родительский объект, который удалит потомка в своем деструкторе или при вызове delete later().

  2. Умный указатель, который ничего не знает об иерархии Qt и попытается удалить объект при выходе из области видимости.

В результате, когда одна из систем удаляет объект первой, вторая остается с «висячей» ссылкой (dangling pointer). Повторная попытка освобождения памяти приведет к аварийному завершению программы (Segmentation fault).
Тут надо заметить что родительский QObject узнает об удаление своего дочернего объекта, и двойного удаления не случится. А вот если объект удалиться через родителя, то умный указатель никак не сможет узнать об этом и произойдет тот самый Double Free.

2. Динамическое дерево объектов и QPointer

Если вам нужно хранить ссылку на объект QObject, которым вы не владеете, и который может удалиться в любое время, то по классике нам нужно использовать QWeakPointer. Но он работает только с QSharedPointer, который, как мы уже выяснили, использовать нельзя.

Здесь на сцену выходит QPointer<T>. Это уникальный для Qt «слабый» указатель:

  • Он не владеет объектом.

  • Он автоматически обнуляется (null), когда целевой QObject удаляется (через delete или родителем).

  • Это возможно благодаря подписке на сигнал destroyed().

Без понимания работы сырых указателей «под капотом» и жизненного цикла QObject правильно использовать этот инструмент невозможно.

3. Сигналы, слоты и sender()

Метод sender(), возвращающий QObject*, — еще одна причина существования сырых указателей. Попытка обернуть этот результат в умный контейнер — это попытка забрать владение у системы, которая им уже управляет. Это кратчайший путь к падению приложения.

4. QML и неявная передача владения (Ownership)

Если вы используете QML, ситуация с указателями становится еще острее. Когда вы передаете QObject* из C++ в QML (через сигналы, методы или свойства), вступает в силу механизм QQmlEngine::ObjectOwnership.

Ловушка JavaScript-владения

Если объект был создан в C++, но у него нет родителя (parent == nullptr), то при передаче в QML движок JavaScript может решить, что теперь он владеет этим объектом:

  1. JavaScript-сборщик мусора (GC) видит, что объект больше не используется в QML-коде.

  2. GC удаляет объект.

  3. Ваш C++ указатель превращается в «тыкву» (dangling pointer).

  4. Результат: Непредсказуемый краш спустя случайное время после вызова GC.

Чтобы этого избежать, разработчики вынуждены либо всегда назначать родителя, либо явно вызывать QQmlEngine::setObjectOwnership(obj, QQmlEngine::CppOwnership). Умные указатели C++ здесь бессильны, так как они не могут контролировать сборщик мусора JavaScript.

А когда, вообще, стоит выбрать умные указатели от Qt, а когда от std?

Если вам все же нужно использовать умные указатели (например, для объектов без родителя), часто возникает дилемма: std::unique_ptr или QScopedPointer? В контексте Qt-разработки у вторых есть преимущества:

  1. Интеграция с контейнерами Qt: Классы вроде QList исторически лучше оптимизированы под работу с объектами, управляемыми через QSharedPointer.

  2. Бинарная совместимость (ABI): Q-аналоги гарантируют стабильность в рамках минорных версий Qt, что важно для разработки библиотек.

  3. Кастомизация удаления: QScopedPointer позволяет удобно использовать QScopedPointerDeleteLater, что критично для объектов вроде QNetworkReply, которые нельзя удалять немедленно.

Несмотря на мощь Qt, стандартная библиотека C++ (STL) в некоторых случаях оказывается эффективнее и правильнее:

  1. Производительность: std::unique_ptr имеет нулевой оверхед (zero-cost abstraction). Он чуть легче, чем QScopedPointer.

  2. Стандартные алгоритмы и библиотеки: Если вы используете сторонние библиотеки (Boost, OpenCV), они ожидают std-указатели.

  3. Бизнес-логика (Domain Model): Код, не завязанный на GUI и QObject, лучше писать на «чистом» C++. Это делает логику переносимой и упрощает юнит-тестирование.

  4. Современные возможности C++: std::shared_ptr поддерживает std::make_shared, что позволяет выделить память под объект и счетчик ссылок одним блоком.

Итоговые рекомендации

Чтобы не запутаться в двух системах управления памятью, следуйте этому простому алгоритму:

  1. Создаете QObject с родителем? Используйте сырой указатель. Памятью управляет родитель. new QLabel("Text", this) — это норма.

  2. Передаете объект в QML? Убедитесь, что у него есть родитель, или явно установите CppOwnership. В QML почти всегда «гуляют» сырые указатели.

  3. Создаете QObject БЕЗ родителя? Используйте QScopedPointer (или std::unique_ptr). Это защитит от утечек.

  4. Нужно следить за объектом, который может быть удален кем-то другим? Используйте QPointer. Это единственный безопасный способ проверить, жив ли еще виджет.

  5. Объект не наследует QObject (Бизнес-логика)? Используйте std::unique_ptr для владения и std::shared_ptr для разделяемых ресурсов.

Заключение

Программа на Qt — это баланс между классическим RAII и иерархическим владением. Попытка использовать только умные указатели — это борьба с фреймворком.

Комментарии (5)


  1. sergio_nsk
    22.04.2026 04:25

    Это наследие компиляторов без шаблонов. Почему не обновят/дополнят API, не понятно. Они, ведь, уже не раз забивали на совместимость.

    QPointer label = parent->AddChild<QLabel>("Hello");


  1. andy_p
    22.04.2026 04:25

    Все эти умные указатели возникли из-за плохо продуманной архитектуры приложения, когда непонятно, кто владелец объекта, кто кого создает и удаляет. В Qt с архитектурой всё в порядке, поэтому такой проблемы не возникает.


  1. cyanidle
    22.04.2026 04:25

    Какая то ужасно дилетантская статья. Скорее всего сгенерирована нейросетью по запросу состоящему из заголовка.
    1) QScopedPointer<QLabel> label(new QLabel("Hello", parent));
    Дабл фри здесь не будет, в деструкторе ребенек убирается себя из списка детей. Зачем здесь ScopedPointer тоже непонятно, можно просто создать объект на стэке, если он будет нужен лишь временно - результат будет тот же (так иногда делают с QMsgBox). Ногострел может быть если только в рамках этого скоупа умрет уже сам родитель.
    2) Любой умный указатель в таком цикле создаст оверхед.
    Умный указатель в цикле просто не нужен, вы получаете невладеющий указатель на сырые байты изображения. Зачем тут вообще указатели если владеет данными сам QImage?
    3)Хотя в новых версиях Qt иногда применяется QScopedPointer, использование std-аналогов там затруднено требованиями к бинарной совместимости (ABI).
    Все намешано в кучу, std умные указатели ничем не хуже кутэшных при создании Pimpl. Человек даже не читал выхлоп нейросети, которая написала полную чушь. Pimpl как раз позволяет избежать проблем ABI и неважно как он реализован, эта идиома используется не только в QT

    И так продолжать можно почти по каждому пункту, статья - мусор


    1. yamix Автор
      22.04.2026 04:25

      Спасибо за замечания! Действительно не доработал и поспешил выложить. Сейчас уже всё поправил.


      1. Mingun
        22.04.2026 04:25

        Ну да, как же. Сначала говорим про QScopedPointer, а через пару слов на голубом глазу у нас QSharedPointer. Дождитесь, пока нейросеть обучится нормально, а потом статьи строчите.