Qt_Code_Style/ImplicitShared.md

8.3 KiB
Raw Permalink Blame History

picture

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
#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.