Russian Qt Forum

Программирование => С/C++ => Тема начата: ksergey85 от Апрель 07, 2015, 17:41



Название: Виртуальные функции
Отправлено: ksergey85 от Апрель 07, 2015, 17:41
Здравствуйте. Возник такой вопрос. Возможно ли какими то средствами (например RTTI) вызвать виртуальный метод непосредственного предка класса объекта над этим объектом.
Поясню. Допустим имеется цепочка наследования классов A->B->C->D->... и т.д. В ходе выполнения программы мы имеем указатель на конкретный объект например класса C. Но сам указатель описан как указатель на объект базового класса A (A *obj). Допустим у класс A есть виртуальный метод, который перекрывается в каждом его предке. Пусть для определенности он называется m. Теперь, когда мы вызываем метод m нашего объекта сработает механизм виртуальных функций и в итоге будет вызван метод C::m над объектом obj. Так вот вопрос как раз в том, как вызвать над объектом obj метод B::m (ведь в таблице виртуальных функций объекта его нет), а если бы это был объект класса D, то метод C::m.
Иерархия в своей основе имеет класс QObject, и даже конструкция obj->metaObject()->superСlass() дает нам метаобъект родительского класса, но вот что с ним дальше делать ума не приложу.  ???


Название: Re: Виртуальные функции
Отправлено: Old от Апрель 07, 2015, 18:07
Именно B::m()  и вызовет метод m из класса B.
Вы даже можете из метода класса D вызвать метод класса A.


Название: Re: Виртуальные функции
Отправлено: ksergey85 от Апрель 07, 2015, 18:16
Вы не поняли. Связывание должно быть не статическим при компиляции, а динамическим в зависимости от того какого класса объект мы имеем, вызваться должна не его виртуальная функция а функция его непосредственного родителя. Как получить ее адрес вот в чем вопрос.


Название: Re: Виртуальные функции
Отправлено: popper от Апрель 07, 2015, 18:31
Иерархия в своей основе имеет класс QObject, и даже конструкция obj->metaObject()->superСlass() дает нам метаобъект родительского класса, но вот что с ним дальше делать ума не приложу.  ???
Предлагаю попробовать следующую связку:

Код
C++ (Qt)
int QMetaObject::indexOfMethod ( const char * method ) const;
QMetaMethod QMetaObject::method ( int index ) const;
bool QMetaMethod::invoke(...)


Название: Re: Виртуальные функции
Отправлено: Nimbus от Апрель 07, 2015, 18:33
Код
C++ (Qt)
A * a = new C();
a->A::m();


Название: Re: Виртуальные функции
Отправлено: Igors от Апрель 08, 2015, 05:40
Иерархия в своей основе имеет класс QObject, и даже конструкция obj->metaObject()->superСlass() дает нам метаобъект родительского класса, но вот что с ним дальше делать ума не приложу.  ???
Приводить
Код
C++ (Qt)
A * a = dynamic_cast <A *> (obj);
if (a)
a->m(); // или a->A::m() и.т.п
 


Название: Re: Виртуальные функции
Отправлено: ksergey85 от Апрель 08, 2015, 09:43
Предлагаю попробовать следующую связку:

Код
C++ (Qt)
int QMetaObject::indexOfMethod ( const char * method ) const;
QMetaMethod QMetaObject::method ( int index ) const;
bool QMetaMethod::invoke(...)

К сожалению метод не является Q_INVOKABLE.

Код
C++ (Qt)
A * a = new C();
a->A::m();

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


Название: Re: Виртуальные функции
Отправлено: ksergey85 от Апрель 08, 2015, 09:45

Код
C++ (Qt)
A * a = dynamic_cast <A *> (obj);
if (a)
a->m(); // &#1080;&#1083;&#1080; a->A::m() &#1080;.&#1090;.&#1087;
 

И так к каждому из возможных вариантов? Тоже выход но тогда теряется универсальность написанной функции в случае если иерархия будет расширена.  :(


Название: Re: Виртуальные функции
Отправлено: ksergey85 от Апрель 08, 2015, 09:50
Если взглянуть с точки зрения компилятора, то задача тривиальная - вызвать функцию с определенным адресом, передав в ecx указатель на объект. Единственная проблема состоит в том, чтобы получить адрес этой функции, то есть адрес метода класса непосредственного предка класса нашего объекта, ведь в таблице виртуальных адресов его нет. Задача как раз состоит в вычислении этого адреса.
Может быть кто-то сможет какие-нить варианты с использованием RTTI предложить? Имя класса целевого мы знаем на этапе исполнения из QMetaObject::superClass()->className(), название метода известно заранее и является статическим в принципе. Можно как то получить адрес метода имея названия класса и метода в виде двух строк?


Название: Re: Виртуальные функции
Отправлено: Igors от Апрель 08, 2015, 10:22
И так к каждому из возможных вариантов? Тоже выход но тогда теряется универсальность написанной функции в случае если иерархия будет расширена.  :(
Приводить придется т.к. с Ваших слов имеете указатель на QObject (который без понятия об иерархии)

Так вот вопрос как раз в том, как вызвать над объектом obj метод B::m (ведь в таблице виртуальных функций объекта его нет), а если бы это был объект класса D, то метод C::m.
Т.е. звать метод класса от которого наследуемся? Ну почему бы просто не передать это в аргументе виртуала, напр
Код
C++ (Qt)
virtual void B::m( bool useParent )
{
if (useParent) return A::m(false);
...
}
 
Или сделать еще один виртуал напр m_prev
 


Название: Re: Виртуальные функции
Отправлено: ksergey85 от Апрель 08, 2015, 10:31
И так к каждому из возможных вариантов? Тоже выход но тогда теряется универсальность написанной функции в случае если иерархия будет расширена.  :(
Приводить придется т.к. с Ваших слов имеете указатель на QObject (который без понятия об иерархии)

Так вот вопрос как раз в том, как вызвать над объектом obj метод B::m (ведь в таблице виртуальных функций объекта его нет), а если бы это был объект класса D, то метод C::m.
Т.е. звать метод класса от которого наследуемся? Ну почему бы просто не передать это в аргументе виртуала, напр
Код
C++ (Qt)
virtual void B::m( bool useParent )
{
if (useParent) return A::m(false);
...
}
 
Или сделать еще один виртуал напр m_prev
 

Я ведь не спорю, что задача, которая передо мной стоит, не решается другим путём. Можно и обходную функцию создать и параметр передать. Просто решение уже не будет таким изящным. В любом языке есть свои ограничения и C++ не исключение, к сожалению.


Название: Re: Виртуальные функции
Отправлено: Igors от Апрель 08, 2015, 11:05
Просто решение уже не будет таким изящным.
Так Вы еще и эстет :) Ну требовать от класса отказа от собственного ф-ционала как-то не очень изящно, да и неестественно


Название: Re: Виртуальные функции
Отправлено: Nimbus от Апрель 08, 2015, 14:00
Еще раз повторю, что статическое связывание не подойдет, т.к. я заранее не могу знать какого конкретно класса придет объект.
Непонятно что за задачу вы решаете, но осмелюсь посоветовать прямое решение -- создать статическую мапу { typeid: boost::function<void(void)>(), ... }.


Название: Re: Виртуальные функции
Отправлено: ksergey85 от Апрель 08, 2015, 15:09
Просто решение уже не будет таким изящным.
Так Вы еще и эстет :) Ну требовать от класса отказа от собственного ф-ционала как-то не очень изящно, да и неестественно

Дело не в этом. Реальная задача конечно сложнее, чем та, которую я изложил здесь. Я убрал все то, что отношения к моему вопросу не имеет. Меня просто интересовало есть такая возможность у языка или нет? Я потому именно в эту ветку свой вопрос затолкал.


Название: Re: Виртуальные функции
Отправлено: ksergey85 от Апрель 08, 2015, 15:11
Еще раз повторю, что статическое связывание не подойдет, т.к. я заранее не могу знать какого конкретно класса придет объект.
Непонятно что за задачу вы решаете, но осмелюсь посоветовать прямое решение -- создать статическую мапу { typeid: boost::function<void(void)>(), ... }.

А можно хотя бы чуточку поподробнее.


Название: Re: Виртуальные функции
Отправлено: Nimbus от Апрель 08, 2015, 17:10
А можно хотя бы чуточку поподробнее.
Если я правильно понял, у вас есть какое-то конечное множество базовых классов и чуть бОльшее множество дочерних классов.
И вам нужно в какой-то одной точке (скорее всего в generic'е) получать указатель на объект дочернего класса и вызывать виртуальный метод его родительского класса.

Так вот, самый танковый метод, сделать статический map (hash) из имён родительских классов и функторов, принимающих только this, вызывающих соответсвующий member function.
Т.е.

Код
C++ (Qt)
static QHash< QString, boost::function<void(QObject*)> > baseMethods;
if( baseMethods.isEmpty() )
{
   baseMethods[ "BaseClass1" ] = boost::bind( & BaseClass1::m, _1 );
   baseMethods[ "BaseClass2" ] = boost::bind( & BaseClass2::m, _1 );
   baseMethods[ "BaseClass3" ] = boost::bind( & BaseClass3::m, _1 );
   ...
   baseMethods[ "BaseClassN" ] = boost::bind( & BaseClassN::m, _1 );
}
 

Ну а затем в точке, где требуется вызывать метод, делаете
Код
C++ (Qt)
QString const baseClassName = obj->metaObject()->superClass().className();
baseMethods[ baseClassName ]( obj );
 

Это, конечно, изврат, но попробовать стоит. Правда, я не уверен, как там будут обстоять дела со множественным наследованием.
Ну либо, имена всех дочерних классов закинуть в map в качестве ключей, а значения продублировать для одинаковых базовых классов.

P.S. Если используете C++11/14 то можно вместо boost использовать std ;-)


Название: Re: Виртуальные функции
Отправлено: Nimbus от Апрель 08, 2015, 17:42
Хотя, насчёт boost::bind я не уверен, возвращаемый тип, скорее всего будет разным. Возможно потребуются дополнительные приседания с qobject_cast к нужным классам.


Название: Re: Виртуальные функции
Отправлено: ksergey85 от Апрель 08, 2015, 18:05
Понял. Ну то есть ручками сохранять адреса методов всех базовых классов. Как один из вариантов обхода лобового решения (ну то есть поиска адреса метода конкретного класса ТОЛЬКО средствами языка или библиотеки Qt) конечно подойдет.
Есть еще вариант сделать этот метод (а в общем случае он конечно будет не один) слотом и вызывать через метаобъект суперкласса, но дело в том, что если это будет достаточно нагруженный работой метод, то я серьезно потеряю в производительности.


Название: Re: Виртуальные функции
Отправлено: Nimbus от Апрель 08, 2015, 18:15
Есть еще вариант сделать этот метод (а в общем случае он конечно будет не один) слотом и вызывать через метаобъект суперкласса, но дело в том, что если это будет достаточно нагруженный работой метод, то я серьезно потеряю в производительности.

Вот как начнёт проседать производительность, тогда и надо об этом задумываться  ;) А пока нужно сделать код как можно более универсальным, чтобы очень легко убрать ту дыру в производительности без особых изменений архитектуры.