Russian Qt Forum

Программирование => С/C++ => Тема начата: ksergey85 от Июль 27, 2012, 11:01



Название: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 27, 2012, 11:01
Здравствуйте. Бьюсь над таким вопросом. Он скорее из области ООП, но имеет отношение и Qt.
Имеем два стандартных Qt-шных класса QTableView и QTreeView, порожденных от одного абстрактного предка QAbstractItemView. Возникла необходимость добавить некоторую функциональность, которая имеет одинаковую реализацию как для QTableView так и для QTreeView. Естественно два раза писать одинаковые методы, перекрыв классы QTableView и QTreeView не хочется. Я перекрываю общего родителя QAbstractItemView, дополняю класс нужной мне функциональностью, и теперь мне необходимо перенести эту дополнительную функциональность в оба дочерних неабстрактных класса с помощью механизма наследования. Как я могу это сделать?
В данный момент блуждаю около виртуального множественного наследования, наступил на такие грабли: классы QTableView и QTreeView порождены от QAbstractItemView не виртуально, поэтому никак не выходит заставить компилятор создать только одну копию класса QAbstractItemView, чтобы он перестал ругаться о неоднозначностях при использовании методов QAbstractItemView.
Как я понимаю в ObjectiveC для подобных ситуаций придуманы категории. Что тут можно придумать на плюсах?


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: mutineer от Июль 27, 2012, 11:10
отнаследовать QTableView и QTreeView от твоего класса, вместо QAbstractItemView. Ну и на обычнах Qt либах это работать не будет - придется делать либо статическую сборку, либо везде носить собственную версию Qt либ с собой


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: Igors от Июль 27, 2012, 11:14
Ну делаете QAbstractItemView * членом Вашего класса и заряжаете его по обстановке QTableView* или QTreeView*. Чем не устраивает этот стандартный прием?


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 27, 2012, 11:24
Ну делаете QAbstractItemView * членом Вашего класса и заряжаете его по обстановке QTableView* или QTreeView*. Чем не устраивает этот стандартный прием?
То есть имеете в виду композицию. Но тогда я не смогу напрямую обращаться к методам QAbstractItemView как к методам моего класса. Или не так?


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: kambala от Июль 27, 2012, 11:27
можно попробовать вынести одинаковую функциональность в отдельный класс и множественно наследоваться от этого класса и QTableView/QTreeView. должно получиться что-то типа такого:
Код
C++ (Qt)
class Common
{
public:
   QVariant data(const QModelIndex &index, int role) const { return "hello"; }
};
 
class TableView : public QTableView, public Common
{
public:
   QVariant data(const QModelIndex &index, int role) const { return Common::data(index, role); }
};
 
class TreeView : public QTreeView, public Common
{
public:
   QVariant data(const QModelIndex &index, int role) const { return Common::data(index, role); }
};
вот только я не знаю как там будет с сигналами и метаобъектной системой в целом
Ну делаете QAbstractItemView * членом Вашего класса и заряжаете его по обстановке QTableView* или QTreeView*. Чем не устраивает этот стандартный прием?
То есть имеете в виду композицию. Но тогда я не смогу напрямую обращаться к методам QAbstractItemView как к методам моего класса. Или не так?
можно написать методы-обёртки, которые просто вызывают нужный метод из QAbstractItemView. или вообще сделать это поле public и обращаться напрямую.


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 27, 2012, 11:28
Похоже я все-таки нашел очень похожую тему: http://www.linux.org.ru/forum/development/4181710. Там решение удовлетворяющее меня частично найдено. Можно зафигачить шаблон класса, где и описать всю общую для классов функциональность.

Цитата:
//NOTE: T is some Q...View class
template<typename T>
class myMegaView:public T{
//some shared code
};

А свои виджеты наследуете так:

class myWidget:public myMegaView<QTreeView>{

};


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: DmitryM от Июль 27, 2012, 11:36
У шаблонных классов не может быть сигнал/слотов.


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 27, 2012, 11:47
To kambala:
Все хорошо пока вам в собственном методе data() не придется использовать методы QAbstractItemView. Под дополнительной функциональностью я подразумевал манипуляции с самим абстрактным вью, который потом должен превратится в конкрентую реализацию либо QTableView либо QTreeView.
На счет оберток - я ж замучаюсь ко всем нужным мне методам писать обертки. И потом если мой класс не будет прямым наследником QAbstractItemView то я не смогу его использовать как полноценный вью там где Qt ожидает от меня вью.


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: Igors от Июль 27, 2012, 11:52
То есть имеете в виду композицию. Но тогда я не смогу напрямую обращаться к методам QAbstractItemView как к методам моего класса. Или не так?
Свой класс наследуете от QAbstractItemView, да, придется "делегироваться", может обильно. Ну ничего страшного, можно потерпеть


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 27, 2012, 11:53
У шаблонных классов не может быть сигнал/слотов.
Откуда информация? Можно ссылку?


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 27, 2012, 11:57
То есть имеете в виду композицию. Но тогда я не смогу напрямую обращаться к методам QAbstractItemView как к методам моего класса. Или не так?
Свой класс наследуете от QAbstractItemView, да, придется "делегироваться", может обильно. Ну ничего страшного, можно потерпеть
Не совсем понял, что вы имеет в виду. То есть мне нужно наследоваться от QAbstractItemView или все-таки сделать QAbstractItemView частью моего класса в виде член-данного? Если второе, то опять же возникает проблема, что мой класс не будет наследником QAbstractItemView, а значит не будет наследником Qwidget и вообще QObject со всеми вытекающими последствиями.


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 27, 2012, 12:07
Кстати шаблоны не подходят. Сигналов/слотов не будет да. Он же вообще не будет наследником QObject   :(
UPDATE: туплю - будет. Вопрос прежний почему не будут работать сигналы и слоты?


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: Igors от Июль 27, 2012, 12:07
То есть мне нужно наследоваться от QAbstractItemView или все-таки сделать QAbstractItemView частью моего класса в виде член-данного?
"и" (вместо "или")


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: DmitryM от Июль 27, 2012, 12:14
У шаблонных классов не может быть сигнал/слотов.
Откуда информация? Можно ссылку?
Это ограничение у moc (http://doc.qt.nokia.com/4.7-snapshot/moc.html#limitations)


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 27, 2012, 12:16
То есть мне нужно наследоваться от QAbstractItemView или все-таки сделать QAbstractItemView частью моего класса в виде член-данного?
"и" (вместо "или")
А можно увидеть как это должно выглядеть. Я что должен буду перекрыть все виртуальные функции обертками над моим QTableView/QTreeView? Не совсем догоняю механизм.


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: Akon от Июль 27, 2012, 15:37
Цитировать
Под дополнительной функциональностью я подразумевал манипуляции с самим абстрактным вью, который потом должен превратится в конкрентую реализацию либо QTableView либо QTreeView.
Это требует доступ к протектед интерфейсу QAbstractItemView.

То, что предлагает Igors, называется "декоратор" (паттерн такой). Он вам не подходит, поскольку позволяет декорировать только паблик интерфейс QAbstractItemView.

Вот это
Цитировать
//NOTE: T is some Q...View class
template<typename T>
class myMegaView:public T{
//some shared code
называется миксином (mixin - подмешиваемая функциональность) и основывается на одной из форм CRTP (идиома C++ такая, см. http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern (http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern)).
Оно будет работать.
Достоинство - есть доступ к протектед интерфейсу.
Недостатки: 1. инстанцирование под каждый наследуемый тип, т.е. раздувание объектного кода (несущественный недостаток), 2. не будет отдельного интерфейса, который соответствует подмешанной функциональности (существенный недостаток, если такой интерфейс требуется); данный недостаток устраняется, путем создания интерфейса и наследования от него, имплементацию интерфейса делает миксин.

Вариант от kambala (класс Common).
Достоинства/недостатки - инверсия предудущего варианта.
Цитировать
Все хорошо пока вам в собственном методе data() не придется использовать методы QAbstractItemView.
Если из класса Common нужно обращение к паблик интерфейсу QAbstractItemView, то делается кросскаст.
Код:
class Common
{
public:
    QVariant data(const QModelIndex &index, int role) const
    {
        QAbstractItemView* thisAsQAbstractItemView = dynamic_cast<QAbstractItemView*>(this);
        thisAsQAbstractItemView->someMethod(...);
    }
};
В частности, кросскастом из Common к QObject можем выбросить любой сигнал из Common (разумеется, окончательный класс должен иметь такой сигнал):
Код:
void Common::setSomeProperty(int value)
{
if (value_ == value) return;
value_ = value;

QObject* object = dynamic_cast<QObject*>(this);  // crosscast
Q_ASSERT(object && "dynamic_cast<QObject*>(this)");

bool ok = QMetaObject::invokeMethod(object, "somePropertyChanged", Qt::DirectConnection,
QGenericReturnArgument(), Q_ARG(int, value));
Q_ASSERT(ok);
Q_UNUSED(ok);
}

Опять таки, в данном варианте нет доступа к протектед интерфейсу QAbstractItemView.


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: Igors от Июль 27, 2012, 16:07
То, что предлагает Igors, называется "декоратор" (паттерн такой). Он вам не подходит, поскольку позволяет декорировать только паблик интерфейс QAbstractItemView.
...
Опять таки, в данном варианте нет доступа к протектед интерфейсу QAbstractItemView.
Может и не подходит (на то и обсуждаем), но аргументацию не понял - если унаследоваться от QAbstractItemView, то protected методы доступны, и методы хранимого указателя тоже


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 27, 2012, 16:59
Шаблонные классы и миксин не подходит из-за ограничений moc.
Кросс-каст интересная тема, но в этом случае класс не будет порожден от QObject и рассчитывать на сигналы и слоты похоже также не приходится? Сигналы и слоты в дополняющем интерфейсе я создать не смогу?


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: Akon от Июль 30, 2012, 07:16
Цитировать
Может и не подходит (на то и обсуждаем), но аргументацию не понял - если унаследоваться от QAbstractItemView, то protected методы доступны, и методы хранимого указателя тоже
Не понял мысль. Протектед интерфейс не доступен через композицию (QAbstractItemView* view_;).

Цитировать
Шаблонные классы и миксин не подходит из-за ограничений moc.
Кросс-каст интересная тема, но в этом случае класс не будет порожден от QObject и рассчитывать на сигналы и слоты похоже также не приходится? Сигналы и слоты в дополняющем интерфейсе я создать не смогу?

Ограничения moc не приятны, было бы хорошо, если бы поддерживалось множественное наследование от QObject. Тем не менее, проблема решается следующим образом: сигналы не объявляются в шаблонных классам, миксинах или в других базовых классах (Common). Сигналы могут объявляться только там где будет Q_OBJECT, т.е. в вашем окончательном классе (классах). Тем не менее, из упомянутых мест сигналы можно выбрасывать.


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: Igors от Июль 30, 2012, 10:26
Не понял мысль. Протектед интерфейс не доступен через композицию (QAbstractItemView* view_;).
А если композитор и сам наследует QAbstractItemView ? (т.е. как бы 2 паттерна в одном). Может где-то заклинит, но рассмотреть стоит.


Название: Re: Вопрос по ООП: расширение функциональнос&
Отправлено: ksergey85 от Июль 30, 2012, 10:46
Хорошо с сигналами слотами потерпеть можно, не так их и много получается. В конце концов приоритетной является задача общего интерфейса.
Пока реализовывал методы с помощью кросскаста возникла проблема. В своем Common перекрыл виртуальный метод setModel() класса QAbstractItemView, но этот метод перекрывают и конкретные реализации, т.е. у классов QTableView и QTreeView он свой. Как мне добраться именно до QTableView::setModel() и QTreeView::setModel().

Имею в Common::setModel(QAbstractItemModel *model)
QAbstractItemView* thisAsView = dynamic_cast<QAbstractItemView*>(this); //crosscast
thisAsView->setModel(model); //вызов самого себя - рекурсия нам не нужна
thisAsView->QAbstractItemView::setModel(model); //статически слинкует с методом класса QAbstractItemView.

Как добраться до перегруженного метода? И реально ли это вообще? ???


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: Akon от Июль 30, 2012, 13:10
Цитировать
А если композитор и сам наследует QAbstractItemView ? (т.е. как бы 2 паттерна в одном). Может где-то заклинит, но рассмотреть стоит.
В этом суть декоратора - внешний интерфейс тот-же, а поведение изменено (с меньшей связностью, нежели при открытом наследовании). Но, повторяю, вы не сможете получить доступ к протектед интерфейсу.

Цитировать
В своем Common перекрыл виртуальный метод setModel() класса QAbstractItemView
Это не перекрытие (виртуального метода). Это создание другого метода. Метод перекрыть можно только из наследника.

Код:
thisAsView->setModel(model); //вызов самого себя - рекурсия нам не нужна
Счего взяли? Это не рукурсия, это полиморфный вызов QAbstractItemView::setModel(). После кросскаста фактическое значение указателя будет другим - соответствующим положению QAbstractItemView в составном классе, соответственно, VMT будет восходить к QAbstractItemView. Кстати, еще ограничение мока - база QObject должна быть первой.

Если знакомы с COM или какой-либо подобной технологией, то кросскаст это аналог QueryInterface().


Название: Re: Вопрос по ООП: расширение функциональности QAbstractItemView.
Отправлено: ksergey85 от Июль 30, 2012, 13:48
Все догнал. То есть по сути виртуальные методы из Common после такого кросс-каста становятся вообще не в счет? У них своя отдельная VMT?
Интересно только как я вызовом thisAsView->setModel(model) мог получить рекурсию при отладке. Но это не важно. Скорее всего дело в том, что мой метод принимает указатель не на объект QAbstractItemModel, а на объект класса-наследника.
P.S. бьюсь над тем как бы покорректнее внедрить слоты и сигналы в Common.