Введение
В проектах на Qt разработчик неизбежно сталкивается с выбором: использовать собственные умные указатели Qt (QSharedPointer, QScopedPointer, QPointer) или указатели стандартной библиотеки (std::shared_ptr, std::unique_ptr, std::weak_ptr). Исторически Qt появился раньше, чем C++11 принёс стандартные умные указатели, поэтому в библиотеке есть собственные аналоги. Сегодня их совместное использование требует понимания тонкостей.
Суть концепции
Qt предоставляет несколько разных инструментов управления временем жизни объектов, и важно не путать их назначение:
QObjectс родителем (parent) — собственная модель владения Qt, где родительскийQObjectавтоматически удаляет всех своих детей в деструкторе. Это не умный указатель, а древовидная модель владения, встроенная прямо вQObject.QPointer<T>— «слабая» ссылка наQObject-потомка, которая автоматически становитсяnullptr, если объект был удалён. Используется только для классов, унаследованных отQObject.QSharedPointer<T>/QWeakPointer<T>— аналогиstd::shared_ptr/std::weak_ptr, подсчёт ссылок, работают с любыми типами, не обязательноQObject.std::shared_ptr<T>/std::unique_ptr<T>/std::weak_ptr<T>— стандартные умные указатели, переносимы между проектами, не зависят от Qt, лучше интегрируются со сторонними C++ библиотеками.
#include <QObject>
#include <QPointer>
#include <QSharedPointer>
#include <memory>
#include <iostream>
// Пример доменного класса, не связанного с QObject —
// для него естественны стандартные умные указатели.
class NetworkSession
{
public:
explicit NetworkSession(const std::string& host) : host_(host)
{
std::cout << "Открыта сессия с " << host_ << std::endl;
}
~NetworkSession()
{
std::cout << "Сессия с " << host_ << " закрыта" << std::endl;
}
void send(const std::string& data)
{
std::cout << "[" << host_ << "] -> " << data << std::endl;
}
private:
std::string host_;
};
// Виджет — наследник QObject, у него своя модель владения через parent
class Worker : public QObject
{
Q_OBJECT
public:
explicit Worker(QObject* parent = nullptr) : QObject(parent) {}
void doWork() { std::cout << "Worker работает" << std::endl; }
};
void demoStdSharedPtr()
{
// Доменная логика без связи с Qt-объектной моделью —
// используем std::shared_ptr, он стандартен и переносим
std::shared_ptr<NetworkSession> session =
std::make_shared<NetworkSession>("example.com");
auto sessionCopy = session; // подсчёт ссылок: refcount == 2
sessionCopy->send("GET /api/data");
}
void demoQPointer()
{
// Worker — QObject, владение задаётся родителем
QObject parentObject;
Worker* worker = new Worker(&parentObject);
// QPointer следит за worker и сам обнулится, если worker удалится
QPointer<Worker> watcher(worker);
if (watcher) {
watcher->doWork();
}
// parentObject удаляется здесь, а вместе с ней — и worker
}
int main()
{
demoStdSharedPtr();
demoQPointer();
return 0;
}
Разбор примера
В demoStdSharedPtr() класс NetworkSession — обычный C++ класс, не имеющий отношения к объектной модели Qt. Для таких классов нет смысла использовать QSharedPointer: std::shared_ptr стандартен, лучше поддерживается инструментами анализа кода, и его проще передавать в код, не зависящий от Qt (например, в общую библиотеку алгоритмов).
В demoQPointer() класс Worker наследуется от QObject и получает владельца через конструктор (parent). Здесь работает родная модель Qt: когда parentObject выходит из области видимости и удаляется, Qt автоматически удаляет всех её детей, включая worker. QPointer<Worker> — это «слабое наблюдение»: после удаления worker указатель watcher сам станет nullptr, и проверка if (watcher) защитит от обращения к мёртвому объекту (избегая classic dangling pointer).
Когда что использовать — практическое правило
- Класс наследуется от
QObjectи встроен в дерево виджетов/объектов → используйте родительскую модель владения Qt иQPointerдля слабых наблюдений. Не оборачивайтеQObjectвQSharedPointer/std::shared_ptr, если у него уже естьparent— это создаст конфликт двух механизмов владения и может привести к двойному удалению. - Класс не наследуется от
QObject, проект тесно завязан на Qt API (например, передаётся в сигналы/слоты, вQVariant, в Qt-контейнеры старого стиля) → можно использоватьQSharedPointer, он интегрируется сQMetaTypeи сериализацией Qt. - Класс не наследуется от
QObject, и код должен быть переносим / тестируем независимо от Qt (бизнес-логика, модели данных, сетевые сессии, парсеры) → используйтеstd::shared_ptr/std::unique_ptr. Это упрощает unit-тестирование без зависимости от QtTest и облегчает миграцию кода между проектами. - Единоличное владение без подсчёта ссылок → для
QObject-объектов без родителя подойдётQScopedPointerили простоstd::unique_ptr(начиная с Qt 5 они взаимозаменяемы по смыслу,std::unique_ptrпредпочтительнее в новом коде).
Подводные камни
- Не смешивайте
QSharedPointerиstd::shared_ptrдля одного и того же объекта — у них разные счётчики ссылок, и объект может быть удалён одним механизмом, пока другой ещё считает его живым. QObjectс родителем нельзя одновременно держать вshared_ptrс обычным удалением — родитель удалит объект сам, аshared_ptrпопытается удалить его повторно. Если такая необходимость всё же есть, используйтеshared_ptrс кастомным delete-функтором, который ничего не делает, либо вообще не назначайтеparent.QPointerработает только сQObject-потомками — для обычных C++ классов используйтеstd::weak_ptr.- В новом коде на Qt 6 многие разработчики сознательно выбирают
std::shared_ptr/std::unique_ptrпо умолчанию для не-QObject-классов, оставляя Qt-указатели только там, где требуется глубокая интеграция с Qt API (сигналы/слоты,QVariant, межпотоковые очереди событий).
================================================================================