Политика конфиденциальностиКонтактыО сайтеОтзывыGitHubDonate
© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB

Идиома "стирания типа" на примере 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. Наследуем от этого класса уже шаблонный класс В, а в нем всего лишь есть:

  • поле в виде указателя на шаблонный тип <T>

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

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

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

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

  • Абстрактный базовый класс и его наследник:

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.

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

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

Комментарии

21 декабря 2017 г. 15:03

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

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

В стандарте С++14 также есть возможность объявлять в качестве аргумента лямбда функции auto аргументы. Которые также шаблонизируются. И если  метод не существуют у объекта, то проект не скомпилируется.
 
Я это к чему всё. Идиома стирания типа вещь хорошая, но при наличии нескольких инструментов можно добиться хорошего баланса. Просто данная идиома из-за шаблонов весьма громоздка получается на вид.
21 декабря 2017 г. 16: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 не разобрался. Но спасибо за совет с лямбдами. Приму на вооружение.

21 декабря 2017 г. 16:27
writable_wiget_ = qobject_cast<QLabel*> (widget); // указатель добавил
23 декабря 2017 г. 10: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
    }
};
То есть присутствует класс только с абстрактными методами. Все классы которые должны реализовывать данный интерфейс наследуются от данного класса, а также от тех классов, функционал которых необходим. Эти классы также можно будет использовать в рамках интерфейса приложения, как и другие виджеты.
Нужно будет только делать каст к классу интерфейса, когда понадобится и должно работать. Ну, конечно, нужно отталкиваться от архитектуры приложения. Если пихать абсолютно все виджеты, то странно получается и непонятно, откуда может прилететь ошибка в будущем.

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
12 декабря 2018 г. 18:49
Yaroslav Chernetskyi

Qt - Тест 001. Сигналы и слоты

  • Результат:31баллов,
  • Очки рейтинга-10
12 декабря 2018 г. 6:19
nikbobrecov

Qt - Тест 001. Сигналы и слоты

  • Результат:57баллов,
  • Очки рейтинга-2
11 декабря 2018 г. 18:59
Feniks3000

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

  • Результат:71баллов,
  • Очки рейтинга1
Последние комментарии
11 декабря 2018 г. 21:01
Евгений Легоцкой

Не знаю, какой-там конкретно эффект и если честно не хочется fl studio ради того, чтобы посмотреть устанавливать, но из того, что увидел в интернете. Предполагаю, что то, что вы хотите с...
11 декабря 2018 г. 19:25
Vlad15007

Подскажите пожалуйста ( я новичок совсем)Можно ли организовать спрайт без этого окошка (как в fl studio fruity dance)?
11 декабря 2018 г. 15:06
Евгений Легоцкой

Что интересно, если написать так from <application_name>.<module_name> import <filename> ,то PyCharm сносит крышу, если разрабатываешь в рамках проекта приложение, ко...
11 декабря 2018 г. 14:52
Илья Чичак

Тут мне тоже есть что сказать=) Сами разрабы советуют импортировать следующим образом: from <application_name> import <module_name> Стоит избегать from . import &l...;
11 декабря 2018 г. 14:28
Евгений Легоцкой

Твоя правда. Согласен. Свои миграции храню в репозитории. На продакшене только выполняю обновление структуры базы данных, после тестирования на дев сервере конечно (читай локальная машина разр...
Сейчас обсуждают на форуме
12 декабря 2018 г. 17:52
Михаиллл

Оказывается оно все переводит в нижний регистр и нужно так писать: SearchTableModel->setTable("\"Test2\"");
12 декабря 2018 г. 16:32
Булат Гиниятов

Допустим в MyObject *myobject = new MyObject; есть метод start(){while(aaa){////////////////}} Как мне обратиться к методу stop(){aaa=false;} ? Соответственно по...
12 декабря 2018 г. 16:28
xintrea

Как выяснилось в этом обсуждении: Отправка Email из Android в Qt для отправки Email в Android необходимо делать Java-метод, который и будет отправлять email. И этот Java-мет...
12 декабря 2018 г. 15:35
lynx

если кому будет вдруг нужно, подумал я над предложением Евгения Но думаю, что проще какой-то функционал дополнить для TableView из Qt Quick Controls 2. вообще берем стандарт...
12 декабря 2018 г. 13:49
Евгений Легоцкой

но у меня нет времени учиться ВЕЧНО давайте без истерик, вы это говорите человеку, который всю жизнь учился, учится и будет учиться, и в программировании всегда так, или учишься и...
Присоединяйтесь к нам в социальных сетях

Для зарегистрированных пользователей на сайте присутствует минимальное количество рекламы