Qt_Code_Style/ImplicitShared.md

255 lines
8.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

![picture](/_resources/images/qtcat.png)
- [К списку документов](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/>