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

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
24 сентября 2018 г. 17:42
edorofeeva

C++ - Тест 001. Первая программа и типы данных

  • Результат 100баллов,
  • Очки рейтинга10
24 сентября 2018 г. 17:37
edorofeeva

C++ - Тест 001. Первая программа и типы данных

  • Результат 66баллов,
  • Очки рейтинга-1
23 сентября 2018 г. 14:38
No Names

C++ - Тест 001. Первая программа и типы данных

  • Результат 60баллов,
  • Очки рейтинга-1
Последние комментарии
24 сентября 2018 г. 15:09
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

А вот здесь у меня есть пример использования supervisor. https://evileg.com/ru/post/3/ Вся статья вам там не интересна, интересен только шаг с настройкой supervisor. Он получается ...
24 сентября 2018 г. 15:00
avovana

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Не могли бы дать ссылку на пример? Какое-то рабочее использование. Т.е. у меня есть Qt Gui App, которое я бы хотел запускать при старте системы и в случае, если оно грохнется. Если о чем Вы го...
24 сентября 2018 г. 14:55
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Если честно, то я не уверен, что это вообще можно реализовать через *.desktop файл. Я сделал предположение на основе того, что вы сказали про *.desktop и рестарт. Все варианты, котор...
24 сентября 2018 г. 14:47
avovana

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Просто сейчас правлю сам файл example.desktop. Пытаюсь понять какую пару key=value мне нужно дописать.
24 сентября 2018 г. 14:42
Евгений Легоцкой

Qt Linux - Урок 001. Автозапуск Qt приложения под Linux

Ну я имел ввиду, что дописать в коде вот сюда то, о чём вы говорили про рестарт QString autorunContent("[Desktop Entry]\n" "Type=Application\n" ...
Сейчас обсуждают на форуме
24 сентября 2018 г. 16:47
Евгений_Канусовский@1981

Чтение файлов в python

Добрый вечер Евгений и форумчане! Столкнулся с проблемой чтения файлов в python: файлы с обычным текстом в формате las и txt читаются, например: ~Version information VERS.          ...
24 сентября 2018 г. 13:29
Евгений Легоцкой

Трансляция видео с помощью VLC по RTP

Добрый день! Я не сталкивался, но предположу, что нужно настроить Input Codec в VLC. В настройках есть секция Input Codec, возможно, что там установлено низкое разрешение. ...
21 сентября 2018 г. 8:25
Евгений Легоцкой

Прокси-модель, содержащая на 1 столбец больше, чем модель-источник.

Попробуйте ещё PySide 2 - это официально поддерживаемый пакет привязок Python к Qt, возможно, что там не будет таких проблем.
20 сентября 2018 г. 20:06
Евгений Легоцкой

Qt Installer Framework

Добрый день. Зачем собирать Qt Installer Framework-то из исходников? Я ещё понимаю Qt собирают из исходников статически (хотя тоже считаю по большей части бесполезной тратой времени),...
Присоединяйтесь к нам в социальных сетях