Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Странное поведение при множественном наследовании QObject
Форум на CrossPlatform.RU > Библиотеки > Qt > Qt Общие вопросы
aljeshishe
#include <QObject>
class A
{
public:
    A(): a(1){};
    int a;

};

class B :   public A, public QObject
{
public:
    B():A(), b(2){};
    int b;
};
int main(int argc, char* argv[])
{
    void* p = new B;
    A* a = (A*)p;
    int c = a->a;
    return 0;
}

В конце значение с неопределено. Если не наследовать от QObject то все нормально с=1. msvc-2008, qt-4.5.0
Litkevich Yuriy
Цитата(aljeshishe @ 1.12.2009, 22:52) *
class B : public A, public QObject
порядок наследования неверный, QObject должен быть всегда первым, читай доку
aljeshishe
Цитата(Litkevich Yuriy @ 1.12.2009, 20:10) *
Цитата(aljeshishe @ 1.12.2009, 22:52) *
class B : public A, public QObject
порядок наследования неверный, QObject должен быть всегда первым, читай доку

Пробовал и так, результат тот же
BRE
Добавь магическую строку:
#include <QObject>
class A
{
public:
    A(): a(1){};
        virtual ~A() {}

    int a;

};

Litkevich Yuriy
BRE, ничего не изменилось

Хм, сделал наследование каком к верху, т.е. "А" первый, тогда получаю единичку.
aljeshishe
Цитата(BRE @ 1.12.2009, 20:55) *
Добавь магическую строку:
#include <QObject>
class A
{
public:
    A(): a(1){};
        virtual ~A() {}

    int a;

};

Спасибо, как я понял дело в полиморфности QObject. Поясните если не трудно для чего нужна полиморфность класса А.

Цитата(BRE @ 1.12.2009, 20:55) *
Добавь магическую строку:
#include <QObject>
class A
{
public:
    A(): a(1){};
        virtual ~A() {}

    int a;

};

BRE
Цитата(aljeshishe @ 1.12.2009, 21:07) *
Спасибо, как я понял дело в полиморфности QObject. Поясните если не трудно для чего нужна полиморфность класса А.

Для того, что-бы компилятор смог правильно вычислить смещение до переменной a.
aljeshishe

Спасибо
BRE
Цитата(Litkevich Yuriy @ 1.12.2009, 21:03) *
BRE, ничего не изменилось

Хм, сделал наследование каком к верху, т.е. "А" первый, тогда получаю единичку.

Тут имеет значение порядок наследования.
В первом случае в памяти имеем (объект класса B ):
int a;
<данные QObject>
int b;

Во втором случае:
<данные QObject>
int a;
int b;

Т.е. смещение переменной a будет разным и компилятор не может правильно рассчитать смещение при обычном привидении типа (void* -> A*).
А вот если указатель сначала привести к B*, а потом к A*, то все будет нормально.
ViGOur
Цитата(BRE @ 1.12.2009, 21:09) *
Для того, что-бы компилятор смог правильно вычислить смещение до переменной a.
Точней будет, что ключевое слово virtual добавляет в класс А таблицу виртуальных методов (vtable), благодаря чему при приведении класса B к классу А смещение считается правильно. А без этого ключевого слова класс А был без этой таблицы. :)

Если я правильно все понял!

А вот и более точный ответ:
Цитата
Обычно компилятор создает отдельную vtable для каждого класса. После создания объекта указатель на эту vtable, называемый виртуальный табличный указатель или vpointer, добавляется как скрытый член данного объекта (а зачастую как первый член). Компилятор также генерирует "скрытый" код в конструкторе каждого класса для инициализации vpointer'ов его объектов адресами соответствующей vtable.
:)
aljeshishe
Цитата(BRE @ 1.12.2009, 21:30) *
Цитата(Litkevich Yuriy @ 1.12.2009, 21:03) *
BRE, ничего не изменилось

Хм, сделал наследование каком к верху, т.е. "А" первый, тогда получаю единичку.

Тут имеет значение порядок наследования.
В первом случае в памяти имеем (объект класса B ):
int a;
<данные QObject>
int b;

Во втором случае:
<данные QObject>
int a;
int b;

Т.е. смещение переменной a будет разным и компилятор не может правильно рассчитать смещение при обычном привидении типа (void* -> A*).
А вот если указатель сначала привести к B*, а потом к A*, то все будет нормально.

Провел несколько экспериментов с порядком наследования, скажите правильно я понял, что:
1. Сначала в памяти идут виртуальные базовые классы в порядке наследования
2. затем невиртуальные классы в порядке наследования
3. в конце идет дочерний класс

И еще вопросик, как располагается в памяти виртуальное наследование ?
ViGOur
Не совсем так, получается примерно так, когда ты создаешь объект класса B создаются аргументы данного класса в такой последовательности:
vpointer *p; // создается неявно компилятором
int a;
int b;
после чего, когда ты приводишь объект класса B к классу A, то аргументу класса A a присваивается значение p (что тот, что другой 4 байта), так как если создать объект класса А, то создается только:
int a;
Если же в классе А добавить ключевое слова virtual деструктору, то создается таблица виртуальных методов и указатель на нее, то есть получается следующая картина:
vpointer *p;
int a;
и все приводится без проблем. :)
BRE
Ой, не являюсь знатоком стандарта, поэтому точно сказать не могу. Может это прописано в стандарте, а может это остается на совести конкретного компилятора.
Попробуй найти эту информацию в книгах/интернете.
Если после своего исследования отпишишся здесь, думаю не только я скажу спасибо.
frg10
Цитата(aljeshishe @ 1.12.2009, 19:52) *
void* p = new B;
A* a = (A*)p;

Не делайте так больше никогда. Компилятор не знает ничего о том, что находится по указателю p, поэтому не может корректно привести типы.
Вот так правильно:
    B* p = new B;
    A* a = (A*)p;
Litkevich Yuriy
Цитата(ViGOur @ 2.12.2009, 2:20) *
Если же в классе А добавить ключевое слова virtual деструктору
правильно ли понимаю, что это ключевое слово не обязательно именно для деструктора, просто нужен хотя бы один виртуальный метод. А в данном примре оказалось удобнее написать его для деструктора, чтобы не изменять другие методы.
ViGOur
Достаточно одного виртуального метода, не обязательно деструктора, даже если этот метод в родительском классе, в производном от него классе, так же создается данная таблица.
aljeshishe
Цитата(frg10 @ 1.12.2009, 23:34) *
Цитата(aljeshishe @ 1.12.2009, 19:52) *
void* p = new B;
A* a = (A*)p;

Не делайте так больше никогда. Компилятор не знает ничего о том, что находится по указателю p, поэтому не может корректно привести типы.
Вот так правильно:
    B* p = new B;
    A* a = (A*)p;


Это была выдержка из того что используется в реальном проекте. В различных сторонних библиотеках некоторые методы могут принимать произвольные данные (void*) и эти же данные возвращать. Поскольку менять библиотеку нехорошо приходится приводить свои типы к void*
P.S. Пример был создан в результате безуспешных попыток передать функции QModelIndex QAbstractItemModel::createIndex ( int row, int column, void * ptr = 0 ) свой объект и забрать его обратно через void * QModelIndex::internalPointer () const
Litkevich Yuriy
Цитата(aljeshishe @ 2.12.2009, 20:40) *
Пример был создан в результате безуспешных попыток передать функции QModelIndex QAbstractItemModel::createIndex ( int row, int column, void * ptr = 0 ) свой объект и забрать его обратно через void * QModelIndex::internalPointer () const
Хм, вроде тут всё относительно просто:
B *p1, *p2;
p1 = new B;
QAbstractItemModel *model = new QAbstractItemModel(....);
model->createIndex(myRow, myColumn, p1);
...
p2 = model->internalPointer();
aljeshishe
Цитата(Litkevich Yuriy @ 2.12.2009, 17:49) *
Хм, вроде тут всё относительно просто:

Согласен, сложного ничего.
В моем случае в дереве хранятся объекты разных типов: пользователи содержат папки, которые содержат сообщения
Пользователи при этом используют сигналы/слоты.
В функциях index, data, parent модели приходится определять какой тип текущего узла. Для этого есть тип Node:
#include <QObject>
class Node
{
public:
    enum NType
    {
        N_USER,
        N_FOLDER,
        N_MSG
    };
    Node(NType tp_): tp(tp_){};
    NType getType() const{return tp;}
    void setType(NType tp_){tp = tp_;}
private:
    NType tp;

};

class Folder : public Node
{
public:
    Folder() : Node(Node::N_FOLDER), b(2){};
    int b;
};
class User : virtual public QObject,  public Node
{
public:
    User():Node(Node::N_USER), b(2){};
    int b;
};
class Msg : public Node
{
public:
    Msg():Node(Node::N_MSG), b(2){};
    int b;
};
int main(int argc, char* argv[])
{
    void* p = new Folder;
    Node* node = (Node*)p;
    return 0;
}

Решил использовать виртуальное наследование вместо полиморфности Node(все равботает). Правильно ли это?


Хорошая статься в тему
http://www.devdoc.ru/index.php/content/view/virtual_base.htm
Litkevich Yuriy
Цитата(aljeshishe @ 2.12.2009, 21:18) *
виртуальное наследование
никогда не слышал про такое
Цитата(aljeshishe @ 2.12.2009, 21:18) *
lass User : virtual public QObject, public Node
За статью спасибо.
aljeshishe
Посмотрел как хранится в памяти класс, наследующий виртуально от полиморфного класса и невиртуально от обычного:
class User : virtual public QObject, public Node

1. Данные Node
2. Адрес виртуального класса QObject - 4 байта
3. Данные User
4. vtable QObject - 4 байта
5. Данные QObject

Таким образом User не содержит первым байтом vtable QObect'a (как при обычном наследовании), поэтому указатели при приведении void* ->Node вычисляются верно
kwisp
поддерживаю fgr10, не было бы
void* p = new B;
A* a = (A*)p;

не было бы проблем:) кстати в куче учебников и книг просто кричат о том что так делать нельзя.
но с другой стороны не было бы и такой интересной и познавательной темы:)
еще в данном вопросе полезно узать как верно пользоваться dynamic_cast<>(), ведь она тоже требует наличия виртуальной таблицы.
Litkevich Yuriy
Цитата(kwisp @ 3.12.2009, 14:38) *
как верно пользоваться dynamic_cast<>()
я пробовал в его коде такое приведение, компилер меня сразу послал, а вто static_cast и reinterpret_cast работают
kwisp
Litkevich Yuriy,
а как послал?
Litkevich Yuriy
вот так:
Цитата
main.cpp:36: error: cannot dynamic_cast `p' (of type `void*') to type `class A*' (source is not a pointer to class)

на строчку:
A *a = dynamic_cast<A*>(p);


вроде синтаксис верный
AD
Я не могу так. Народ, будьте внимательнее.
"Странное поведение при множественном наследовании QObject" вместо
"Странное повдение при множественном наследовании QObject"

P.S. Как то следите хотя бы за названиями тем. Про сообщения я молчу.... Извините, если надоедаю по пустякам....
kwisp
Цитата(Litkevich Yuriy @ 3.12.2009, 16:06) *
вроде синтаксис верный

Цитата
оператор dynamic_cast поддерживает преобразования типов во время выполнения, обеспечивая безопасную навигацию по иерархии классов. Он позволяет трансформировать указатель на базовый класс в указатель на производный от него, а также преобразовать l-значение, ссылающееся на базовый класс, в ссылку на производный, но только в том случае, если это завершится успешно;


Однако для получения информации о типе производного класса операнд любого из операторов dynamic_cast должен иметь тип класса, в котором есть хотя бы одна виртуальная функция.
Litkevich Yuriy
kwisp, т.е. в обратную сторону он не преобразует (как в данном примере) от наследника к базовому классу?
kwisp
Litkevich Yuriy,
в данном примере ты пытался его заставть преобразовать из типа void* !
dynamic_cast служит не только дял преобразования указателей и ссылок с l-value но и для определения возможно ли преобразование в случае неудачи с указателями возвращает 0 в слкчае неудачи с сылками выкидывает исключение.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2024 IPS, Inc.