Ошибка "ambiguous call to overloaded function" для виртуальных функций - почему ?

 

Друзья, помогите !  

Проблема:

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

В классе объявлю две виртуальные функции:

virtual void GetData(CiOpen* poOpen);
virtual void GetData(CiClose* pcClose);

 

Каждая из этих функций  берет данные из соответствующего объекта-таймсерии, и сохраняет во внутренней переменной.

Теперь объявляем функцию, в которую будем передавать указатель на нужный объект ( то ли CiOpen, то ли CiClose): 

double GetResult(CSeries * psSeries)
   {
   GetData(psSeries);

   return (m_dInnerData);

 }

 

Однако, пишет ошибку "ambiguous call to overloaded function" .

Странно - я думал, в этом и заключается позднее связывание - что функции определяются в момент создания объекта, а выходит, что нам уже сейчас, во время компиляции надо знать, какой именно объект будет вызывать данную ВИРТУАЛЬНУЮ функцию. При этом если одну из функций убрать (скажем, оставить только одну функцию с CiOpen) - то все в порядке, несмотря на передачу указателя CSeries,  вызывается правильная функция с объектом CiOpen.

Что-то я не вполне пойму концепцию виртуальных функций...

Интересно, что если объявить функцию  

virtual void GetData(CSeries* pcClose)

То никаких проблем не возникает, однако, и никакой витуальности тоже - просто вызывается эта, последняя функция... А как же с виртуальностью ?

 
Laryx:

Теперь объявляем функцию, в которую будем передавать указатель на нужный объект ( то ли CiOpen, то ли CiClose): 

double GetResult(CSeries * psSeries)
   {
   GetData(psSeries);

   return (m_dInnerData);

 }

 

а кто чей потомок?

Если CSeries родитель, то вы должны перед вызовом GetData определить таки чье же оно.

и преобразовать его в зависимости от типа пришедшего psSeris

CiOpen * iop=psSeries;
GetData(iop);

CiClose * icl=psSeries;

GetData(icl);


А как определить принадлежность пришедшего psSeries в GetResult - к iOpen или iClose  - можете придумать сами.

 

Благодарю за оперативный ответ

sergeev:

 

а кто чей потомок?

Ну, в Стандартной Библиотеке общий предок - это CSeries,  а CiOpen и CiClose - потомки. Я этим же и пользуюсь. Собственно, хотел сделать что-то вроде паттерна "Посетитель" - чтобы можно было универсально доставать данные из любого объекта-наследника от CSeries.

Если CSeries родитель, то вы должны перед вызовом GetData определить таки чье же оно.

и преобразовать его в зависимости от типа пришедшего psSeris

CiOpen * iop=psSeries;
GetData(iop);

CiClose * icl=psSeries;

GetData(icl);

А как определить принадлежность psSeries к iOpen или iClose  - можете придумать сами.

То есть, сам CiOpen или CiClose - "не знает", кто он такой, и какие функции ему принадлежат ? То есть, все, даже виртуальные функции должны быть известны уже на этапе компиляции ?

А в чем тогда виртуальность функции ? В примере из документации (с фигурами тетрис) вызывается m_shape.Draw() - причем, как я понимаю, сама переменная m_shape - как раз имеет тип предка, а вовсе не одного из потомков.

Я думал, что виртуальные функции тем и отличаются, что конкретная перегруженная функция определяется во время создания объекта - и дальше, несмотря на то, что указатель преобразовывается к указателю на предка, функция остается прежней. Значит, в MQL - так нельзя ?  

 

 
Laryx:

Благодарю за оперативный ответ

Ну, в Стандартной Библиотеке общий предок - это CSeries,  а CiOpen и CiClose - потомки. Я этим же и пользуюсь. Собственно, хотел сделать что-то вроде паттерна "Посетитель" - чтобы можно было универсально доставать данные из любого объекта-наследника от CSeries.

То есть, сам CiOpen или CiClose - "не знает", кто он такой, и какие функции ему принадлежат ? То есть, все, даже виртуальные функции должны быть известны уже на этапе компиляции ?

А в чем тогда виртуальность функции ? В примере из документации (с фигурами тетрис) вызывается m_shape.Draw() - причем, как я понимаю, сама переменная m_shape - как раз имеет тип предка, а вовсе не одного из потомков.

Я думал, что виртуальные функции тем и отличаются, что конкретная перегруженная функция определяется во время создания объекта - и дальше, несмотря на то, что указатель преобразовывается к указателю на предка, функция остается прежней. Значит, в MQL - так нельзя ?  

 

Вы немного по разному интерпретируете виртуальность функций. Сравните даже Ваш пример и то, что Вы делаете:

1) пример.

Есть множество фигур (которые все наследники от Shape). у каждой есть метод draw(), который был и у предка. (метод функции!)

Теперь можно задать массив фигур Shape, запихнуть туда разные фигуры (фигуры-потомки) и для каждой вызвать ее метод draw

Это и есть виртуальность


2) ваша попытка

Есть два потомка (open и close) и две функции (с одинаковым названием), одна принимает объект класса open, вторая объект класса close.

При этом эти два класса имеют общего предка.

И вот вы вызываете для предка функцию. Вопрос какую из?

 

И вот вы вызываете для предка функцию. Вопрос какую из?

Ну, я думал - того объекта, который был создан. По крайней мере, на MSVC такая конструкция очень даже работает. Ведь виртуальность заключается в том, что при создании объекта, он обладает таблицей виртуальных функций, в которой хранятся указатели на функции объекта, и эта таблица живет во время всей жизни объекта. Указатель на объект можно привести к указателю на потомка, таблица функций от этого не изменится.

Значит, здесь -  иначе.

Хорошо.  

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

Как я понял, надо сделать моих потомков от CiOpen  и CiClose (назовем их СiMyOpen и СiMyClose, и от других нужных таймсерий и индикаторов), у всех определить метод PutDataTo(CStorage* pStorage).

И определить класс CStorage, в котором мои потомки будут сохранять свои данные.

После этого - определяем массив потомков, и у всех вызываем одну и ту же фукнцию PutDataTo(CStorage* pStorage), каждый потомок сам заполняет хранилище так, как он считает нужным. 

А я дальше уже использую данные из этого хранилища.

Получается так ?  

Но только у кого вызывать данный метод ? Ведь у CSeries его не было ! Не хотелось бы вносить изменения в станадартную библиотеку (добавив такой метод)

 

 

 
Laryx:

Ну, я думал - того объекта, который был создан. По крайней мере, на MSVC такая конструкция очень даже работает. Ведь виртуальность заключается в том, что при создании объекта, он обладает таблицей виртуальных функций, в которой хранятся указатели на функции объекта, и эта таблица живет во время всей жизни объекта. Указатель на объект можно привести к указателю на потомка, таблица функций от этого не изменится.

Значит, здесь -  иначе.

Хорошо.  

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

Как я понял, надо сделать моих потомков от CiOpen  и CiClose (назовем их СiMyOpen и СiMyClose, и от других нужных таймсерий и индикаторов), у всех определить метод PutDataTo(CStorage* pStorage).

И определить класс CStorage, в котором мои потомки будут сохранять свои данные.

После этого - определяем массив потомков, и у всех вызываем одну и ту же фукнцию PutDataTo(CStorage* pStorage), каждый потомок сам заполняет хранилище так, как он считает нужным. 

А я дальше уже использую данные из этого хранилища.

Получается так ?  

так. и это работает нормально.

но это совсем не то, о чем вы спросили вначале и показали участок кода с ошибкой.


 
sergeev:

так. и это работает нормально.

но это совсем не то, о чем вы спросили вначале и показали участок кода с ошибкой.

 

 

Ну... Моя цель - достичь желаемого, если я ошибся - меня поправят знающие люди (вот, кстати, спасибо за консультацию). 

Остался вопрос - вот эту функцию PutDataTo(CStorage* pStorage) - у кого вызывать ? Ведь в массиве хранятся разнородные объекты, с общим предком CSeries, но у этого предка-то не было такой функции !

Похоже, не получается... 

 
Laryx:

Ну... Моя цель - достичь желаемого, если я ошибся - меня поправят знающие люди (вот, кстати, спасибо за консультацию). 

Остался вопрос - вот эту функцию PutDataTo(CStorage* pStorage) - у кого вызывать ? Ведь в массиве хранятся разнородные объекты, с общим предком CSeries, но у этого предка-то не было такой функции !

Похоже, не получается... 

Если не хотите менять предка CSeries, то (так как нет множественного наследования) делайте класс-заглушку между ним и вашими классами в котором и объявите виртуальную функцию

Естественно в массиве храните объекты этого класса-заглушки уже, а не CSeries

 

Laryx:

Остался вопрос - вот эту функцию PutDataTo(CStorage* pStorage) - у кого вызывать ?

у всех.

Ведь в массиве хранятся разнородные объекты, с общим предком CSeries, но у этого предка-то не было такой функции !

ну так добавьте! в чем проблема то?
 
Laryx:

При чем здесь вообще виртуальность и наследование?

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

Неоднозначность надо убирать руками.

sergeev:

ну так добавьте! в чем проблема то?

В "стандартную" библиотеку? Звучит дико, для меня по крайней мере.


 
sergeev:

у всех.

ну так добавьте! в чем проблема то?

 

Проблема в том, что я не хочу изменять базовые классы Стандартной Библиотеки. Собственно, и TheXpert об этом уже сказал. Придется, как тут предложили, делать "класс-заглушку".

TheXpert:

При чем здесь вообще виртуальность и наследование?

Есть две равнозначные перегрузки функции. При передаче базового класса в качестве параметра компилятор не знает какую из них выбрать,

Виртуальность при том, что компилятор вроде как и не должен знать, какую из функций выбирать. В этом же и состоит суть позднего связывания, что нужная функция выбирается не компилятором, на этапе сборки программы, а во время работы программы, в момент создания объекта.

 

Ну, так или иначе, мне главное было понять, как сделать желаемое. Разобрался. Всем премного благодарен за участие и рекомендации.

 

Причина обращения: