Russian Qt Forum

Программирование => С/C++ => Тема начата: wxmaper от Сентябрь 04, 2015, 05:57



Название: Приведение к типу
Отправлено: wxmaper от Сентябрь 04, 2015, 05:57
Есть вот такой незамысловатый header:
Код:
#define PQ_OBJECT \
public:\
    QStringList myList;

class PQWidget;
class PQWidget : public QWidget {
    Q_OBJECT
    PQ_OBJECT

public:
    Q_INVOKABLE explicit PQWidget(QWidget *parent = 0)
        : QWidget(parent){}
    virtual ~PQWidget(){}
};

class PQPushButton;
class PQPushButton : public QPushButton {
    Q_OBJECT
    PQ_OBJECT

public:
    Q_INVOKABLE explicit PQPushButton(QWidget *parent = 0)
        : QPushButton(parent) {
        myList << "text1";
        myList << "text2";
    }
};

и код:
Код:
// QObject -> PQWidget
    QObject *button = new PQPushButton;
    PQWidget *pbutton = (PQWidget*) button;

    if(pbutton->myList.contains("text1")) {
        qDebug() << "Okay!";
    }

    qDebug() << "QObject -> PQWidget OK!";

вроде написано всё честно и справедливо - PQWidget является потомком QWidget, как и кнопка. И все они после моего наследования имеют поле myList. Код работает. И какой бы виджет я не писал с добавлением макроса PQ_OBJECT, будь то различные кнопки, текстовые поля, контейнеры и... короче, если взять абсолютно любой виджет, он прекрасно преобразуется в PQWidget и не выпендривается :)

А вот второй похожий случай:
Код:
class PQObject;
class PQObject : public QObject {
    Q_OBJECT
    PQ_OBJECT

public:
    Q_INVOKABLE explicit PQObject(QObject *parent = 0)
        : QObject(parent){}
    virtual ~PQObject(){}
};

class PQTimer;
class PQTimer : public QTimer {
    Q_OBJECT
    PQ_OBJECT

public:
    Q_INVOKABLE explicit PQTimer(QObject *parent = 0)
        : QTimer(parent) {
        myList << "text1";
        myList << "text2";
    }
};

Код:
// QObject -> PQObject
    QObject *timer = new PQTimer;
    PQObject *ptimer = (PQObject*) timer;

    if(ptimer->myList.contains("qwerty")) { // Segmentation fault
        qDebug() << "Okay!";
    }

    qDebug() << "QObject -> PQObject OK!";

Все написано в точности так же как и с QWidget. Полная аналогия: PQObject и QTimer - потомки QObject. PQObject и PQTimer имеют поле myList. На этапе преобразования QObject -> PQObject программа работает, но при попытке обратится к какому либо полю из PQ_OBJECT - сегфолт.

Ребята, прокомментируйте, пожалуйста, такое поведение? Почему в первом случае код работает, а во втором - нет? Должен ли он вообще был работать или это случайность, которая оказалась случайной для всех виджетов? :)

П.с. хочу заметить, что унаследоваться от PQObject , как по идее и должно было быть, я не могу, потому что Qt не допускает двойное наследование Q_OBJECT, а мой PQObject обязательно должен быть наследником QObject.

П.п.с. макрос обрезал для читаемости, на самом деле в нем намного больше полей и методов и все они доступны (проверено) при приведении к PQWidget.


Название: Re: Приведение к типу
Отправлено: Igors от Сентябрь 04, 2015, 11:07
Код:
    QObject *timer = new PQTimer;
    PQObject *ptimer = (PQObject*) timer;
Для PQObject
Цитировать
VMT
поля QObject
PQObject   (myList)

Для PQTimer
Цитировать
VMT
поля QObject
поля QTimer
PQObject   (myList)
Поэтому работать не должно, т.к. myList сидит по разным смещениям. Почему работает для PQPushButton - не знаю, возможно нет полей QPushButton (хранит все в "d"). В любом случае затея с PQObject (Widget) некорректна, надо использовать множественное наследование и потом кросс-приводиться


Название: Re: Приведение к типу
Отправлено: wxmaper от Сентябрь 04, 2015, 11:14
А что это за вывод вы привели? Что за VMT?


Название: Re: Приведение к типу
Отправлено: Igors от Сентябрь 04, 2015, 11:17
А что это за вывод вы привели? Что за VMT?
Это формат объектов в памяти, VMT - указатель на таблицу виртуалов

Edit: есть же простой способ проверить!
Код
C++ (Qt)
qDebug() << sizeof(QWidget);
qDebug() << sizeof(QPushButton);
qDebug() << sizeof(QObject);
qDebug() << sizeof(QTimer);
 


Название: Re: Приведение к типу
Отправлено: wxmaper от Сентябрь 04, 2015, 11:23
Это формат объектов в памяти, VMT - указатель на таблицу виртуалов
Мне больше интересно как вы это сообщение получили :) как мне самому это все увидеть? Первый раз о таком слышу ^_^

надо использовать множественное наследование

Я не могу сообразить как корректно организовать множественное наследование из-за ограничений Qt: нельзя дважды наследовать Q_OBJECT. PQObject мне нужен как обертка над QObject, PQTimer как обертка над QTimer и т.д. их много. И у всех должны быть методы или поля именно PQObject'a, поэтому пришлось выкручиваться через макросы. А PQObject должен наследоваться от QObject, иначе нет доступа к метасистеме Qt... в обещем раскрутить этот клубок у меня не получается.


Название: Re: Приведение к типу
Отправлено: Racheengel от Сентябрь 04, 2015, 11:29
Не, множественное наследование не прокатит из-за Q_OBJECT. Надо теплейтами делать.


Название: Re: Приведение к типу
Отправлено: Igors от Сентябрь 04, 2015, 11:34
Мне больше интересно как вы это сообщение получили :) как мне самому это все увидеть? Первый раз о таком слышу ^_^
Создайте экземпляр класса и посмотрите его в отладчике

Я не могу сообразить как корректно организовать множественное наследование из-за ограничений Qt: нельзя дважды наследовать Q_OBJECT. PQObject мне нужен как обертка над QObject, PQTimer как обертка над QTimer и т.д. их много. И у всех должны быть методы или поля именно PQObject'a, поэтому пришлось выкручиваться через макросы. А PQObject должен наследоваться от QObject, иначе нет доступа к метасистеме Qt... в обещем раскрутить этот клубок у меня не получается.
Код
C++ (Qt)
struct PQObject {    // не наследуемся от QObect
QStringList myList;
};
 
class PQTimer : public QTimer, public PQObject {
...
};
Теперь напр в какой-то ф-ции Вы имеете только PQObject
Код
C++ (Qt)
void SomeFunc( PQObject * po )
{
QObject * obj = dynamic_cast <QObect *> (po); // кросс-приведение
if (obj) {  // это множ унаследлванный po
..
}
else {  // а этот нет
...
}
}


Название: Re: Приведение к типу
Отправлено: wxmaper от Сентябрь 04, 2015, 11:40
Ну это понятно! Только кроме PQTimer мне нужен еще и сам PQObject со всеми плюшками Q_OBJECT'a...

Код:
// есть не только такой код:
QWidget *button= new PQPushButton;
PQWidget *pbutton = (PQWidget*) button;

// есть еще и такой:
QWidget *widget= new PQWidget;
PQWidget *pwidget= (PQWidget*) widget;

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


Название: Re: Приведение к типу
Отправлено: wxmaper от Сентябрь 04, 2015, 11:50
Информация в отладчике очень противоречивая :/
как видно, и у QObject *timer, и у PQObject *ptimer поле myList существует...

(http://s019.radikal.ru/i602/1509/e4/b7e8a4438f81.png)


Название: Re: Приведение к типу
Отправлено: Igors от Сентябрь 04, 2015, 12:06
Ну это понятно! Только кроме PQTimer мне нужен еще и сам PQObject со всеми плюшками Q_OBJECT'a...

Код:
// есть не только такой код:
QWidget *button= new PQPushButton;
PQWidget *pbutton = (PQWidget*) button;

// есть еще и такой:
QWidget *widget= new PQWidget;
PQWidget *pwidget= (PQWidget*) widget;

Если я создам структуру не унаследованную от QObject, то я не смогу получить доступ к метасистеме для обычного QObject или QWidget - вот в чем проблема  ???
Это популярное заблуждение. "Ах, нельзя делать множ наследование от QObject! Вот если б не было такого ограничения....". Предположим это так, и, (о чудо!) это разрешено. И мы бодро пишем напр
Код
C++ (Qt)
class CDream : public QWidget, public QTimer {
...
};
Но как это должно работать? Мы сможем связать сигнал или со слотом QWidget или QTimer. Все тоже для мета-системы - или один базовый класс или другой. И, получив сигнал, нам все равно нужно ломиться из одного базового в другой.  Какой же "выйгрыш"? Да никакого, зато новых забот полно - ведь "просто так" уже не законнектиться

В общем, "продумывайте архитектуру". Напр никто не мешает сделать так
Код
C++ (Qt)
class BasePObject : public QObject, public MyData {
Да, в методах MyData придется кросс-приводиться, но это не смертельно.


Название: Re: Приведение к типу
Отправлено: Igors от Сентябрь 04, 2015, 12:15
Информация в отладчике очень противоречивая :/
как видно, и у QObject *timer, и у PQObject *ptimer поле myList существует...
Создайте "чистый" (new PQObject) и его смотрите - у него нет полей таймера. А поскольку Вы привели "в стиле С" то смещение myList будет вычисляться как для PQObject


Название: Re: Приведение к типу
Отправлено: wxmaper от Сентябрь 04, 2015, 12:49
И мы бодро пишем

Уж не знаю как вы так бодро пишете, но вот такое никогда не скомпилируется:
Код:
class PQObject : public QObject {
    Q_OBJECT

public:
    explicit PQObject(QObject *parent = 0)
        : QObject(parent){}

    virtual ~PQObject(){}

    QStringList myList;
};

class PQPushButton : public QPushButton, public PQObject {
public:
    explicit PQPushButton(QWidget *parent = 0)
        : QPushButton(parent)
    {
    }

    virtual ~PQPushButton(){}
};

QObject *button = new PQPushButton; // ошибка: 'QObject' is an ambiguous base of 'PQPushButton'

Получается мне придется создавать PQObject:
Код:
PQObject *button = new PQPushButton;
а его я уже никуда не смогу пропихнуть...



Название: Re: Приведение к типу
Отправлено: wxmaper от Сентябрь 04, 2015, 12:55
а его я уже никуда не смогу пропихнуть...

ооо какую же я фигню сморозил :)) PQObject пре-прекрасно кастуется в QObject! :D
Да я еще и Q_OBJECT везде совал, а он нужен только PQObject'y... да уж! Спасибо вам, Igors, вы меня спасли от жутких макросов и в корне неверных кастов :)))
Самое печальное в этой ситуации то, что проблема вылезла когда проект вырос до размеров огромного неповоротливого слона... таких объектов у меня примерно штук 30-40 %( буду исправлять