Ordinary Mind
Ordinary Mind21 декабря 2017 г. 5:55

Идиома "стирания типа" на примере QWidget

Type erasure idiom

Статья предполагает наличие у читателя базовых знаний C++


Сразу к делу.

Предположим, что вам нужно динамически ( например, как мне, по нажатию кнопки ) изменять текст
нескольких объектов, таких как:

  1. QLabel
  2. QPushButton
  3. QLineEdit

Большинство из вас знает, что эти виджеты наследуют QWidget, и поэтому почему бы не объявить метод, который принимает указатель на этот базовый класс, а потом уже вызывать метод setText?

QWidget * base_pointer = new QLabel();
base_pointer->setText("ERROR");

Проблема в том, что в QWidget не определен метод setText(), и поэтому сделать у вас это не получится.

Мой знакомый посоветовал мне 2 варианта решения этой задачи:

  • Через идиому "стирания типа"
  • Через сигналы и слоты

О сигналах и слотах вы уже точно знаете, и поэтому я расскажу о 1 способе. Кратко.

Идиома "стирания типа" (type erasure idiom) позволяет стереть тип любого объекта, скрыв его за
объектом более общего типа.

Привет шаблоны

Как это выглядит на словах:

  1. Создаем абстрактный базовый класс А, в нем объявляем виртуальную функцию SetText
  2. Наследуем от этого класса уже шаблонный класс В, а в нем всего лишь есть:

  3. поле в виде указателя на шаблонный тип

  4. реализация виртуального метода SetText

  5. В классе-клиенте объявляем указатель типа А

  6. Используем :)

Как это выглядит на деле:

  • Абстрактный базовый класс и его наследник:
namespace om_animation {
class AbstractWritableWidget {
public:
    ~AbstractWritableWidget() {}
    virtual void SetText(const QString& text) = 0;
};

template <class Widget>
class WritableWidget : public AbstractWritableWidget {
public:
    WritableWidget(Widget* writable_widget) : writable_widget_(writable_widget) {}
    ~WritableWidget() {}

    void SetText(const QString& text) override {
        writable_widget_->setText(text);
  }

private:
    Widget* writable_widget_ = nullptr;
};
}
  • Используем ( просто пример ):
class Client {
public:
    template <typename T>
    void Run(T * type){
        // do what you want
        writable_widget_ = new WritableWidget<T> (type);
        writable_widget_->SetText("Success");
        // do what you want
    }

private:
     AbstractWritableWidget* writable_widget = nullptr;
}
  • Реальный код:
template <typename Widget>
void om_animation::TextAnimator::RunAnimation(Widget* widget) {
  if (WritableMatcher::IsWidgetWritable(widget->metaObject()->className())) {
    if (writable_widget_) {
      delete writable_widget_;
    }
    writable_widget_ = new WritableWidget<Widget>(widget);
    timer_->start(animation_delay_);
  } else {
    throw std::logic_error(
        "incompatible type for text animation, add type to "
        "WritableMatcher class");
  }
}

В реальном коде я еще сделал класс со статическими методами для проверки какой класс поддерживает
метод setText в библиотеке Qt. Там просто вектор под капотом. Ничего сложного.
Через метаобъект получаем имя класса, сравниваем с уже имеющимся в списке. Да - да, нет - нет.

Далее я произвел простую проверку указателя, и собственно уже само использование.
Если объект не был найден, клиент получает exception.

Вот и все. Я нахожу эту идиому очень крутой.

Спасибо за внимание.

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Evgenii Legotckoi
  • 21 декабря 2017 г. 10:03

Идиома достаточно интересная, единственное, код кажется весьма громоздким.

Полагаю, что в большинстве случаев можно было бы обойтись наследованием интерфейса, чтобы реализовать определённые метода, а также делать попытку static_cast к этому интерфейсному классу. Если static_cast будет успешен, то получаем объект интерфейса, в противном случае nullptr. В одном случае выполняем необходимые действия. во втором ничего не делаем.

В стандарте С++14 также есть возможность объявлять в качестве аргумента лямбда функции auto аргументы. Которые также шаблонизируются. И если  метод не существуют у объекта, то проект не скомпилируется.
 
Я это к чему всё. Идиома стирания типа вещь хорошая, но при наличии нескольких инструментов можно добиться хорошего баланса. Просто данная идиома из-за шаблонов весьма громоздка получается на вид.
Ordinary Mind
  • 21 декабря 2017 г. 11:25

Спасибо за отзыв. Очень приятно, что кто-то прочитал и ответил.

У меня была проблема, я хотел что бы мой код-клиент, с помощью вызова всего лишь одного метода мог изменять текстовое содержание любого виджета Qt, в котором определен метод setText. Поэтому я не мог обойтись наследованием интерфейса, под этим ведь это понимается?
class WritableWidget : public QLabel{
Q_OBJECT
public:
void setText(const QString & text) override { // something }
};
Ведь мне нужно туда передавать и QPushButton и QLineEdit и еще свои виджеты тоже, которые являются наследниками от определенных в библиотеке Qt.

Я пробовал сделать вот так:
void WritableWidget::AnimateText(QWidget * widget){
// ранее объявленный указатель как поле класса
writable_wiget_ = qobject_cast<QLabel> (widget);
}
Я не помню, но вроде все хорошо кастовалось. Но опять же, я туда передаю QLabel, но мне нужна была универсальность.

На счет 14 стандарта даже не думал . . . Пока еще с 11 не разобрался. Но спасибо за совет с лямбдами. Приму на вооружение.

Ordinary Mind
  • 21 декабря 2017 г. 11:27
writable_wiget_ = qobject_cast<QLabel*> (widget); // указатель добавил
Evgenii Legotckoi
  • 23 декабря 2017 г. 5:21

Не совсем.
Под наследованием интерфейса понимается вот что

class Interface
{
    virtual void setText(QString str) = 0;
};


class A : public QLabel, Interface
{
    virtual void setText(QString str) override
    {
        // Todo Something
    }
};

class B : public QPushButton, Interface
{
    virtual void setText(QString str) override
    {
        // Todo Something
    }
};

class C : public QWidget, Interface
{
    virtual void setText(QString str) override
    {
        // Todo Something
    }
};
То есть присутствует класс только с абстрактными методами. Все классы которые должны реализовывать данный интерфейс наследуются от данного класса, а также от тех классов, функционал которых необходим. Эти классы также можно будет использовать в рамках интерфейса приложения, как и другие виджеты.
Нужно будет только делать каст к классу интерфейса, когда понадобится и должно работать. Ну, конечно, нужно отталкиваться от архитектуры приложения. Если пихать абсолютно все виджеты, то странно получается и непонятно, откуда может прилететь ошибка в будущем.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Дмитрий

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:60баллов,
  • Очки рейтинга-1
Дмитрий

C++ - Тест 003. Условия и циклы

  • Результат:92баллов,
  • Очки рейтинга8
d
  • dsfs
  • 26 апреля 2024 г. 1:56

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии
k
kmssr8 февраля 2024 г. 15:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко4 февраля 2024 г. 22:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 7:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 5:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik18 декабря 2023 г. 18:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
G
George136 мая 2024 г. 21:27
добавить qlineseries в функции в функции: "GPlotter::addSeries(QString title, QVector &arr)" я вызываю метод setChart(...), я в конструктор передал адрес на QChartView элемент
BlinCT
BlinCT5 мая 2024 г. 2:46
Написать свой GraphsView Всем привет. В Qt есть давольно старый обьект дял работы с графиками ChartsView и есть в 6.7 новый но очень сырой и со слабым функционалом GraphsView. По этой причине я хочу написать х…
PS
Peter Son3 мая 2024 г. 14:57
Best Indian Food Restaurant In Cincinnati OH Ready to embark on a gastronomic journey like no other? Join us at App india restaurant and discover why we're renowned as the Best Indian Food Restaurant In Cincinnati OH . Whether y…
Evgenii Legotckoi
Evgenii Legotckoi2 мая 2024 г. 11:07
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Добрый день. По моему мнению - да, но то, что будет касаться вызовов к функционалу Андроида, может создать огромные трудности.
IscanderChe
IscanderChe30 апреля 2024 г. 1:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…

Следите за нами в социальных сетях