- 1. Привет шаблоны
- 1.
- 2. Реальный код:
Type erasure idiom
Статья предполагает наличие у читателя базовых знаний C++
Сразу к делу.
Предположим, что вам нужно динамически ( например, как мне, по нажатию кнопки ) изменять текст
нескольких объектов, таких как:
- QLabel
- QPushButton
- QLineEdit
Большинство из вас знает, что эти виджеты наследуют QWidget, и поэтому почему бы не объявить метод, который принимает указатель на этот базовый класс, а потом уже вызывать метод setText?
QWidget * base_pointer = new QLabel(); base_pointer->setText("ERROR");
Проблема в том, что в QWidget не определен метод setText(), и поэтому сделать у вас это не получится.
Мой знакомый посоветовал мне 2 варианта решения этой задачи:
- Через идиому "стирания типа"
- Через сигналы и слоты
О сигналах и слотах вы уже точно знаете, и поэтому я расскажу о 1 способе. Кратко.
Идиома "стирания типа" (type erasure idiom) позволяет стереть тип любого объекта, скрыв его за
объектом более общего типа.
Привет шаблоны
Как это выглядит на словах:
- Создаем абстрактный базовый класс А, в нем объявляем виртуальную функцию SetText
Наследуем от этого класса уже шаблонный класс В, а в нем всего лишь есть:
поле в виде указателя на шаблонный тип
реализация виртуального метода SetText
В классе-клиенте объявляем указатель типа А
- Используем :)
Как это выглядит на деле:
- Абстрактный базовый класс и его наследник:
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.
Вот и все. Я нахожу эту идиому очень крутой.
Спасибо за внимание.
Идиома достаточно интересная, единственное, код кажется весьма громоздким.
В стандарте С++14 также есть возможность объявлять в качестве аргумента лямбда функции auto аргументы. Которые также шаблонизируются. И если метод не существуют у объекта, то проект не скомпилируется.
Спасибо за отзыв. Очень приятно, что кто-то прочитал и ответил.
Не совсем.
Под наследованием интерфейса понимается вот что