8.3 KiB
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.
#pragma once
#ifndef NAYK_ERROR_OBJECT_H
#define NAYK_ERROR_OBJECT_H
#include <QtCore/QMetaType>
#include <QtCore/QString>
#include <QtCore/QDateTime>
#include <QtCore/QSharedDataPointer>
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<ErrorObjectPrivate> d;
}
Q_DECLARE_METATYPE(ErrorObject)
#endif // NAYK_ERROR_OBJECT_H
- Файл с реализацией
error_object.cpp
содержит код нашего классаErrorObject
и реализацию закрытого классаErrorObjectPrivate
#include "error_object.h"
#include <QtCore/QSharedData>
// Закрытый класс 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<ErrorObject>()) )
qRegisterMetaType<ErrorObject>();
}
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;
}
- Пример использования в коде:
#include "error_object.h"
//...
ErrorObject error(1, 404, "Resource not found");
//... какие-то действия
ErrorObject otherError = error; // здесь данные не копируются, увеличивается счетчик ссылок
qDebug() << "Error code:" << otherError.code();
otherError.setCode(200); // здесь происходит копирование данных, затем изменение в копии
Для возможности использования типа в метасистеме Qt
добавлено объявление типа в .h
файле:
Q_DECLARE_METATYPE(ErrorObject)
и регистрация типа (должна вызываться один раз до использования в метасистеме):
qRegisterMetaType<ErrorObject>();
После регистрации тип ErrorObject
можно использовать для передачи в сигналах и
для конвертации в/из QVariant
.