Идиома "стирания типа" на примере 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.

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

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

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

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

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

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

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

У меня была проблема, я хотел что бы мой код-клиент, с помощью вызова всего лишь одного метода мог изменять текстовое содержание любого виджета 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 не разобрался. Но спасибо за совет с лямбдами. Приму на вооружение.

writable_wiget_ = qobject_cast<QLabel*> (widget); // указатель добавил

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

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
    }
};
То есть присутствует класс только с абстрактными методами. Все классы которые должны реализовывать данный интерфейс наследуются от данного класса, а также от тех классов, функционал которых необходим. Эти классы также можно будет использовать в рамках интерфейса приложения, как и другие виджеты.
Нужно будет только делать каст к классу интерфейса, когда понадобится и должно работать. Ну, конечно, нужно отталкиваться от архитектуры приложения. Если пихать абсолютно все виджеты, то странно получается и непонятно, откуда может прилететь ошибка в будущем.

Comments

Only authorized users can post comments.
Please, Log in or Sign up
N
June 25, 2019, 2:41 p.m.
Nico03

C++ - Test 001. The first program and data types

  • Result:40points,
  • Rating points-8
S
June 25, 2019, 9:16 a.m.
SabaNtuy

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:40points,
  • Rating points-8
SZ
June 24, 2019, 5:49 p.m.
Serg Zhi

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

  • Result:78points,
  • Rating points2
Last comments
June 24, 2019, 10:23 a.m.
Евгений Легоцкой

Хорошо, ну будут проблемы помимо того, что касается статей, то не стесняйтесь задавать вопросы на форуме.
МБ
June 24, 2019, 10:21 a.m.
Михаил Булатов

Извиняюсь, все работает(из-за невнимательности).
June 24, 2019, 9:52 a.m.
Евгений Легоцкой

Придётся делать ещё сигнал в дочернем qml и пробрасывать через коннекты и обработчики. А вообще нужно смотреть конкретный код и что вы пытаетесь сделать. Так что лучше будет, если вы зад...
June 21, 2019, 8:31 a.m.
Ruslan Polupan

Вот моя строка по которой все отлично сработало %cqtdeployer% -bin c:/CentralMposKeys/CentalMposKeys.exe -qmake c:/Qt/5.12.2/mingw73_64/bin/qmake.exe
June 21, 2019, 8:24 a.m.
Андрей Янкович

Возможно кому то пригодится сqtdeployer для windows работает точно так же как и для Linux разница лишь в команде запуска Linux: cqtdeployer Windows: %cqtdeployer...
Now discuss on the forum
June 25, 2019, 6:16 p.m.
Алексей Внуков

только через webengine, прямого апи у Яндекса нет, вроде что-то есть у гугла, сам только начал интересоваться этим вопросом
June 25, 2019, 5:05 p.m.
Михаиллл

Само заработало. Странно.
June 25, 2019, 2:32 p.m.
Михаиллл

Похоже глюк вебсокета. К другим вебсокетам подключаюсь.
June 25, 2019, 1:55 p.m.
Андрей Янкович

падало потому что boolStatus был на стеке метода, после завершения метода переменная убивалась, и на обращении к ней было падение.просто сделай вот так: connect(&t, &QTimer::timeou...
June 25, 2019, 10:55 a.m.
IscanderChe

По пункту 3 попытался переписать метод setData. В итоге комбобокс перестал работать. bool MySqlTableModel::setData(const QModelIndex& index, const QVariant& value, int /* role */){...
Looking for a Job?
10,000.00 руб. - 15,000.00 руб.
Нужен помощник для создания API.
Moscow, Moscow, Russia
25,000.00 руб. - 30,000.00 руб.
Разработчик Qt/C++
Barnaul, Altai Krai, Russia

For registered users on the site there is a minimum amount of advertising

EVILEG
About
Services
Join us
© EVILEG 2015-2019
Recommend hosting TIMEWEB