255 lines
8.3 KiB
Markdown
255 lines
8.3 KiB
Markdown

|
||
|
||
- [К списку документов](Readme.md)
|
||
|
||
# Implicit shared в Qt
|
||
|
||
Многие классы C++ в Qt используют неявное совместное использование данных для
|
||
максимального использования ресурсов и минимизации копирования.
|
||
Неявно совместно используемые классы являются как безопасными, так и эффективными,
|
||
когда передаются в качестве аргументов, поскольку передается только указатель на данные,
|
||
а данные копируются только тогда, когда функция записывает в них данные, т. е.copy-on-write.
|
||
|
||
Implicit sharing в Qt — это механизм, при котором при копировании классов
|
||
не происходит копирование данных, а копирование происходит лишь тогда,
|
||
когда копии класса потребуется изменить эти данные.
|
||
|
||
<br/>
|
||
|
||
Некоторые особенности механизма:
|
||
|
||
- При создании общего объекта счётчик ссылок устанавливается в 1.
|
||
- Когда новый объект ссылается на общие данные, счётчик ссылок увеличивается.
|
||
- Когда объект теряет ссылку на общие данные, счётчик ссылок уменьшается.
|
||
- Общие данные удаляются, когда счётчик ссылок становится равен 0.
|
||
|
||
Implicit sharing реализован во многих классах C++ в Qt.
|
||
|
||
<br/>
|
||
|
||
При разработке таких классов используется паттерн Pimpl.
|
||
|
||
<br/>
|
||
|
||
## Pimpl
|
||
|
||
Pimpl — Pointer to private implementation.
|
||
Это одно из названий паттерна программирования.
|
||
Еще его называют чеширским котом — «Cheshire Cat» (это название
|
||
мне больше нравится). В чем суть этого паттерна?
|
||
Основная идея этого паттерна — это вынести все приватные члены класса и,
|
||
в некоторых случаях, функционала в приватный класс.
|
||
|
||
<br/>
|
||
|
||
В Qt коде используется подход `d`-указателей.
|
||
Смысл в том что объявляется класс `XXXPrivate` и переменная публичного класса
|
||
в защищенной секции. В отдельном заголовочном файле или в `.cpp` файле уже
|
||
пишется реализация приватного класса.
|
||
Иерархия классов идет как по публичным так и по приватным классам.
|
||
Для этого объявление приватного класса обычно делается в отдельном `.h` файле,
|
||
который называется так-же как публичный, но добавляется
|
||
приставка `_p`: `qclassname_p.h`. И эти классы не устанавливаются вместе с
|
||
библиотекой, а служат лишь для сборки библиотеки.
|
||
|
||
<br/>
|
||
|
||
## Пример класса с использованием 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 <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`](examples/error_object.cpp) содержит код нашего класса `ErrorObject`
|
||
и реализацию закрытого класса `ErrorObjectPrivate`
|
||
|
||
```C
|
||
#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;
|
||
}
|
||
```
|
||
|
||
- Пример использования в коде:
|
||
|
||
```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>();
|
||
```
|
||
|
||
После регистрации тип `ErrorObject` можно использовать для передачи в сигналах и
|
||
для конвертации в/из `QVariant`.
|
||
|
||
<br/>
|
||
|
||
---
|
||
|
||
<br/>
|
||
|
||
- [К списку документов](Readme.md)
|
||
|
||
<br/>
|
||
|
||
<br/>
|
||
|