Russian Qt Forum

Qt => Общие вопросы => Тема начата: ecspertiza от Март 07, 2012, 19:00



Название: Выполнить init() всем потомкам класса
Отправлено: ecspertiza от Март 07, 2012, 19:00
Есть довольно интересная иерархия виджетов, есть базовый виджет от которого наследуются остальные. В базовом виджете должны проверяться некоторые условия, и если они валидны, то должна вызваться ф-цтя init() естественно в базовом виджете она виртуальная, а у наследников переопределена. Для решения этой задачи я накидал следующий код


widget.h
Код:
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QDebug>

class WidgetPrivate : public QWidget
{
    Q_OBJECT

    public:
        WidgetPrivate(QWidget *parent = 0);


        void setSource(QString arg);

    signals:
        void created();


    private:
        QString m_source;

    private slots:
        void cr();
};

template<typename Derived>
class Widget : public WidgetPrivate
{
    public:
        Widget(Derived *d,QWidget *parent = 0);


    protected:
        void mousePressEvent(QMouseEvent *);
        virtual void init();
};

class FirstWidget : public Widget<FirstWidget>
{
    public:
        FirstWidget(QWidget *parent = 0);

        void init();
};

class LastWidget : public Widget<LastWidget>
{
    public:
        LastWidget(QWidget *parent = 0);

        void init();
};


#endif // WIDGET_H


widget.cpp
Код:
#include "widget.h"

WidgetPrivate::WidgetPrivate(QWidget *parent)
    :QWidget(parent)
{
    connect(this,SIGNAL(created()),this,SLOT(cr()));
    emit created();
}

void WidgetPrivate::cr()
{
    qDebug() << "create widget";
}

void WidgetPrivate::setSource(QString arg)
{
    m_source = arg;
}

template<typename Derived>
Widget<Derived>::Widget(Derived *d,QWidget *parent)
    :WidgetPrivate(parent)
{
    qDebug() << Q_FUNC_INFO;
}

template<typename Derived>
void Widget<Derived>::mousePressEvent(QMouseEvent *)
{
    qDebug() << Q_FUNC_INFO;
}

FirstWidget::FirstWidget(QWidget *parent)
    :Widget<FirstWidget>(this,parent)
{
    qDebug() << Q_FUNC_INFO;
}

void FirstWidget::init()
{

}

LastWidget::LastWidget(QWidget *parent)
    :Widget<LastWidget>(this,parent)
{
    qDebug() << Q_FUNC_INFO;
}

void LastWidget::init()
{
    setWindowTitle("Last");
}


и main.cpp
Код:
#include <QtGui/QApplication>
#include <QWidget>

#include <QDebug>

#include "widget.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    FirstWidget f;
    f.setSource("f");
    f.show();

    LastWidget l;
    l.setSource("l");
    l.show();

    return a.exec();
}

компилятор хавает код нормально, а вот линкер ругается на

Код:
debug/main.o:main.cpp:(.rdata$_ZTV6WidgetI10LastWidgetE[vtable for Widget<LastWidget>]+0xe8): undefined reference to `Widget<LastWidget>::init()'
debug/main.o:main.cpp:(.rdata$_ZTV6WidgetI11FirstWidgetE[vtable for Widget<FirstWidget>]+0xe8): undefined reference to `Widget<FirstWidget>::init()'

стало быть возникают два вопроса, почему ругается ? и как победить ?

полный пример прилагаю (в аттаче)


Название: Re: Выполнить init() всем потомкам класса
Отправлено: ecspertiza от Март 07, 2012, 20:33
Я обычно шаблонами вообще не пользуюсь, но тут возникла след. задача. Нужно для приложения реализовать два интерфейса
1. под винду и написан на декларативе  (qml)
2. под мак нативный

Стало быть я и сделал 1 базовый класс. который проверяет ос, и если это винда то создает дикларативный вьювер, если это мак то вызывает функцию init() в которой и будет происходить создание виджета под мак. вот ХЗ как это было по другому сделать :)

А пример ваш работает, спасибо :)


Название: Re: Выполнить init() всем потомкам класса
Отправлено: ecspertiza от Март 07, 2012, 20:53
так и есть, но это здесь такое поведение если винда то интерфейс на декларативе если мак то нативный, стало быть базовый виджет все это проверяет и если нужно вызывает init для мака


Название: Re: Выполнить init() всем потомкам класса
Отправлено: ecspertiza от Март 07, 2012, 21:09
Конструктор базового виджета

Код:
template <typename Derived>
BasicWindow<Derived>::BasicWindow(Derived *d,QWidget *parent)
    :BasicWindowPrivate(parent)
{
    #ifndef Q_OS_MAC || Q_OS_MACX || Q_OS_MAC64 || Q_OS_DARWIN || Q_OS_DARWIN64
        m_layout = new QVBoxLayout(this);
        m_layout->setMargin(0);
        m_layout->setSpacing(0);
        m_viewer = new QDeclarativeView(this);
        m_viewer->viewport()->installEventFilter(this);
        m_layout->addWidget(m_viewer);

        m_viewer->setResizeMode(QDeclarativeView::SizeRootObjectToView);
        m_viewer->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing);

        m_viewer->rootContext()->setContextProperty("window",this);
        m_viewer->rootContext()->setContextProperty("core",Core::instance());

        QObject::connect(m_viewer->rootContext()->engine(), SIGNAL(quit()), QCoreApplication::instance(), SLOT(quit()));

        m_viewer->setMouseTracking(true);
    #else
        m_viewer = NULL;
        d->setRoundRect(false);
        d->init();
    #endif

}

коли под виндой мы, создаем на видете декларатив и принимаем qml, коли под маком мы для потомка вызываем init() который создает нативные компоненты.


Название: Re: Выполнить init() всем потомкам класса
Отправлено: ecspertiza от Март 07, 2012, 21:58
Не совсем, эта конструкция появилась по той причине, что для каждого наследника нужно вызвать init() , в шаблоне передаем наследника, и для него по необходимости вызывается init() - то есть создаются нужные компоненты, если без этой конструкции то нужно было бы у каждого виджета проверять на ос и т.д. (то что происходит в базовом виджете), копипаст меня не радует.


Название: Re: Выполнить init() всем потомкам класса
Отправлено: niXman от Март 08, 2012, 17:55
ИМХО, это плохой стиль. Обычно подобные конструкции свидетельствуют об архитектурных ошибках
правда? :o
классики негодуют: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern


Название: Re: Выполнить init() всем потомкам класса
Отправлено: niXman от Март 08, 2012, 19:00
Остаюсь при своём мнении.
и это правильно. вот только оглашать этого не надо. глупо выказывать свою не_эрудированность/не_образованность ;)


Название: Re: Выполнить init() всем потомкам класса
Отправлено: niXman от Март 09, 2012, 18:47
ты совершенно прав.
но сам себя-то ты и не приметил, называя один из паттернов кривым стилем, ты считаешь себя умнее классиков.


Название: Re: Выполнить init() всем потомкам класса
Отправлено: niXman от Март 09, 2012, 19:14
Цитировать
Тот, кто следует шаблонам, никогда не создаст ничего нового.
школота атакует ;D

и много нового ты создал чтоб тебя где-то публиковали?
ты кем-то/где-то/в_чем-то признанный эксперт? ну хоть в чем-то? хоть в выращивании плесени? ;D

Александреску/Саттер тебе не ровня, естественно. и не ты себя считаешь умнее других :facepalm


Название: Re: Выполнить init() всем потомкам класса
Отправлено: Igors от Март 09, 2012, 19:31
Конструкция template "на себя" возможна, хотя я ее пользовал всего раз или 2. На мой взгляд основной минус template - капитально затрудняется понимание. Вот и здесь - я честно прочитал тему минимум дважды, посмотрел исходники, в них все понятно. Но, убей бог, я совершенно не понял ЗАЧЕМ же нужен этот template, т.е. почему нельзя обойтись виртуалом   :)

2kyv есть "особи" с которыми лучше не разговаривать вообще (следуя правилу зоны). Если разговор сваливается в дешевые понты (типа "ты не знаешь а вот Я знаю!") - то незачем отвечать такому (вернее "такой")


Название: Re: Выполнить init() всем потомкам класса
Отправлено: niXman от Март 09, 2012, 19:52
мальчик
тоньше нужно быть! тоньше! ;D

оставляю последнее слово за тобой
ох как же это дешево. ни ума ни фантазии....


Название: Re: Выполнить init() всем потомкам класса
Отправлено: niXman от Март 09, 2012, 19:54
2kyv есть "особи" с которыми лучше не разговаривать вообще (следуя правилу зоны). Если разговор сваливается в дешевые понты (типа "ты не знаешь а вот Я знаю!") - то незачем отвечать такому (вернее "такой")
маладца! иди отмалчивайся в тряпочку. я дам знать когда тебе будет позволено открыть рот ;)


Название: Re: Выполнить init() всем потомкам класса
Отправлено: Sahab от Март 09, 2012, 20:06
Обычно подобные конструкции свидетельствуют об архитектурных ошибках.
Да ну? Вы вообще давно с программированием связаны?

Цитировать
1) Тот, кто следует шаблонам, никогда не создаст ничего нового.
А также напишет очень много бажных-никому-не-нужных велосипедов.


Название: Re: Выполнить init() всем потомкам класса
Отправлено: niXman от Март 09, 2012, 20:07
я совершенно не понял ЗАЧЕМ же нужен этот template, т.е. почему нельзя обойтись виртуалом   :)
можно увидеть пример?


Название: Re: Выполнить init() всем потомкам класса
Отправлено: Igors от Март 09, 2012, 20:42
Цитировать
1) Тот, кто следует шаблонам, никогда не создаст ничего нового.
А также напишет очень много бажных-никому-не-нужных велосипедов.
Если "шаблон" употреблено в широком смысле (ведь это не обязательно "template") то я согласен. И уж лучше велосипед (со всеми его багами) чем "вычитанная умность", которая не проверена на себе. 


Название: Re: Выполнить init() всем потомкам класса
Отправлено: Sahab от Март 09, 2012, 20:59
Цитировать
Если "шаблон" употреблено в широком смысле (ведь это не обязательно "template")
Именно это я и имел в виду.

Цитировать
И уж лучше велосипед (со всеми его багами) чем "вычитанная умность"
Спорно. Ко всему конечно нужно подходить с умом, однако девелопер потратит время, оплачиваемое кстати, на написание велосипеда (со всеми его багами). И не факт, что велосипед будет работать лучше и качественнее.

Цитировать
которая не проверена на себе.
А что мешает ее проверить?


Название: Re: Выполнить init() всем потомкам класса
Отправлено: Sahab от Март 09, 2012, 21:03
Взять к примеру паттерны банды четырех. Хотите сказать, что это "неправильная умность" и не нужно их использовать?
А ведь с ними можно такую жесть наворотить, если их неправильно применять.


Название: Re: Выполнить init() всем потомкам класса
Отправлено: Igors от Март 09, 2012, 21:38
Спорно. Ко всему конечно нужно подходить с умом, однако девелопер потратит время, оплачиваемое кстати, на написание велосипеда (со всеми его багами).
Хмм.. не всегда платят "за время" иногда и "за результат" (свои плюсы-минусы)

И не факт, что велосипед будет работать лучше и качественнее.
По сравнению с чем? Откуда уверенность что есть уже готовое решение для любой задачи? Это для студентов ("как сделать правельно"), а для реальных задач... Часто кажется - ну вот же, иногда сам это уже делал. Но при ближайшем рассмотрении выплывают "детали" - которые казались мелкими/незначительными, но, оказывается, они все меняют. И вот тут с (за)Знайками большие проблемы. Сначала у них срабатывает рефлекс на "написано" (как в опытах Павлова). Невозможно отговорить такого, все, он "знает". Но потом выясняется что это "не совсем то", а еще потом - "совсем не то". Тогда начинается претензии к постановке задачи, скандалы и.т.п. Ведь "делать самому" - позорно.  Увы, таков часто результат обильных знаний и обширной эрудиции.



Название: Re: Выполнить init() всем потомкам класса
Отправлено: Akon от Март 11, 2012, 08:31
ТС: Конкретно по коду, ваша ошибка заключается в том, что у шаблона класса Widget<T> функция init объявлена, но не определена. Например, если сделаете ее чисто виртуальной, ошибка будет снята.
Код:
template<typename Derived>
class Widget : public WidgetPrivate
{
...
virtual void init() = 0;
...
}

Я так понимаю, вы хотите сделать т.н. "статический полиморфизм" для вызовов функций приватного класса, в частности, init(). Тогда зачем эта функция виртуальная? Ваш код мне не совсем понятен - в чем смысл приватного класса, если от него идет открытое наследование? Где в коде вызывается init()?. Просьба, опишите задачу более подробно.
 


Название: Re: Выполнить init() всем потомкам класса
Отправлено: ecspertiza от Март 11, 2012, 09:05
Должен быть примерно след. шаблон http://liveworkspace.org/code/e679e6c4df8079a5303dce4c027d048a приватный класс который есть в коде сделан только за счет того что классы с Q_OBJECT не поддерживают работу с шаблонами, а так как всетаки нужны сигналы и слоты пришлось извратиться написав приватный класс. Вопрос на самом деле уже решен, только из топика половина ответов прибито. В аттач выложу последний рабочий код. А задача стояла в том что бы под разными платформами рисовать разный интерфейс под маком нативный под виндой и линухом декларатив.


Название: Re: Выполнить init() всем потомкам класса
Отправлено: Akon от Март 11, 2012, 10:20
Приведенный по ссылке шаблон (как есть) нерабочий в принципе, в смысле не соответствует задуманному. Вызов виртуального метода из конструктора - только не для С++! Желаемого поведения можно добиться, в частности, с помощью CRTP (одну из форм вы привели выше), но это потенциально бажный путь, поскольку наследник в общем случае не будет полностью создан. В С++ такая задача решается выделением полиморфного функционала в отдельный класс и использованием стратегии (паттерн).

Цитировать
А задача стояла в том что бы под разными платформами рисовать разный интерфейс под маком нативный под виндой и линухом декларатив.
Ну, типичная задача. А чем не устраивает подход, используемый в Qt (бридж)? Что в вашем случае дают шаблоны - элегантность, гибкость, меньший набор исходного кода, быстродействие?


Название: Re: Выполнить init() всем потомкам класса
Отправлено: ecspertiza от Март 11, 2012, 10:25
Наверное не совсем представляю что за Qt (бридж) поэтому и решил сделат как есть. Если можно пример буду рад


Название: Re: Выполнить init() всем потомкам класса
Отправлено: Akon от Март 11, 2012, 10:48
Мост (Bridge) - широко используемый паттерн (описан в банде четырех), когда необходим единый интерфейс (паблик класс) для нескольких реализаций (приватный класс). Для примеров см. сорцы Qt, например, qwidget*.



Название: Re: Выполнить init() всем потомкам класса
Отправлено: ecspertiza от Март 11, 2012, 10:48
Спасибо, гляну этот паттерн


Название: Re: Выполнить init() всем потомкам класса
Отправлено: Igors от Март 11, 2012, 11:15
Неясно как в данном случае должен выглядеть этот мост, и не натолкнется ли он на проблемы с QObject и/или делегирование окажется слишком массивным. Вообще непонятно почему нельзя отделаться #ifdef ?


Название: Re: Выполнить init() всем потомкам класса
Отправлено: ecspertiza от Март 11, 2012, 11:17
можно отделаться #ifdef , только придется в каждом виджете это проверять, а так проверка происходит только в одном месте :)