Ordinary Mind
Dec. 21, 2017, 4:55 p.m.

Idiom "type erasure" on the example of QWidget

Type erasure idiom

The article assumes that the reader has basic knowledge of C++


Straight to the point.

Suppose you need to dynamically (like me, on button click) change the text
several objects such as:

  1. QLabel
  2. QPushButton
  3. QLineEdit

Most of you know that these widgets inherit from QWidget, so why not declare a method that takes a pointer to this base class and then call the setText method?

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

The problem is that QWidget doesn't define the setText() method, so you can't do that.

My friend advised me 2 options for solving this problem:

  • Via the "type erasure" idiom
  • Through signals and slots

You already know for sure about signals and slots, and therefore I will talk about 1 method. Briefly.

The type erasure idiom allows you to erase the type of any object by hiding it behind
object of a more general type.

Hello templates

How it looks in words:

  1. We create an abstract base class A, in it we declare the virtual function SetText
  2. We inherit from this class the already template class B, and it only has:

  3. field in the form of a pointer to a template type

  4. implementation of the virtual method SetText

  5. In the client class, we declare a pointer of type A

  6. Use :)

How it looks in practice:

  • An abstract base class and its successor:
  1. namespace om_animation {
  2. class AbstractWritableWidget {
  3. public:
  4. ~AbstractWritableWidget() {}
  5. virtual void SetText(const QString& text) = 0;
  6. };
  7.  
  8. template <class Widget>
  9. class WritableWidget : public AbstractWritableWidget {
  10. public:
  11. WritableWidget(Widget* writable_widget) : writable_widget_(writable_widget) {}
  12. ~WritableWidget() {}
  13.  
  14. void SetText(const QString& text) override {
  15. writable_widget_->setText(text);
  16. }
  17.  
  18. private:
  19. Widget* writable_widget_ = nullptr;
  20. };
  21. }
  • We use (just an example):
  1. class Client {
  2. public:
  3. template <typename T>
  4. void Run(T * type){
  5. // do what you want
  6. writable_widget_ = new WritableWidget<T> (type);
  7. writable_widget_->SetText("Success");
  8. // do what you want
  9. }
  10.  
  11. private:
  12. AbstractWritableWidget* writable_widget = nullptr;
  13. }
  • Real code:
  1. template <typename Widget>
  2. void om_animation::TextAnimator::RunAnimation(Widget* widget) {
  3. if (WritableMatcher::IsWidgetWritable(widget->metaObject()->className())) {
  4. if (writable_widget_) {
  5. delete writable_widget_;
  6. }
  7. writable_widget_ = new WritableWidget<Widget>(widget);
  8. timer_->start(animation_delay_);
  9. } else {
  10. throw std::logic_error(
  11. "incompatible type for text animation, add type to "
  12. "WritableMatcher class");
  13. }
  14. }

In real code, I also made a class with static methods to check which class supports
setText method in Qt library. There's just a vector under the hood. Nothing complicated.
Through the metaobject, we get the name of the class, compare it with the one already in the list. Yes - yes, no - no.

Further I have made simple check of the pointer, and actually already use.
If the object was not found, the client receives an exception.

That's all. I find this idiom very cool.

Thank you for your attention.

By article asked0question(s)

2

Do you like it? Share on social networks!

Evgenii Legotckoi
  • Dec. 21, 2017, 9:03 p.m.

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

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

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

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

У меня была проблема, я хотел что бы мой код-клиент, с помощью вызова всего лишь одного метода мог изменять текстовое содержание любого виджета 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
  • Dec. 21, 2017, 10:27 p.m.
writable_wiget_ = qobject_cast<QLabel*> (widget); // указатель добавил
Evgenii Legotckoi
  • Dec. 23, 2017, 4:21 p.m.

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

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
  • Last comments
  • AK
    April 1, 2025, 11:41 a.m.
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    March 9, 2025, 9:02 p.m.
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    March 9, 2025, 4:14 p.m.
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…
  • ИМ
    Nov. 22, 2024, 9:51 p.m.
    Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
  • Evgenii Legotckoi
    Oct. 31, 2024, 11:37 p.m.
    Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup