Как вызвать функцию динамически с аргументом полиморфизма

Как я могу динамически вызывать функцию вида:childA.function(childB) в то время как их статические типы у обоих родителей?

и с более подробной информацией:

У меня есть физический проект, где мне нужно рассчитать потенциал 2 молекул. Но у меня есть 2 типа молекул, LC и Col, и у каждого типа есть свои параметры и прочее, однако я хочу иметь возможность динамически вызывать потенциал каждой молекулы.

поэтому я попробовал это:

#include <iostream>
#include <typeinfo>
#include <stdio.h>

using namespace std;
class Col;
class LC;

class Molecule
{
    public:
    /// more data and functions should be here regarding the molecule
    double someBulshit;
    virtual double potential(const Molecule * mol){}
    virtual double potential(const Col * mol){}
    virtual double potential(const LC * mol){}
};

class LC : public Molecule
{
    public:
    /// more data and functions should be here regarding the LC molecule
    virtual double potential(const Molecule * mol) {return 1;}
    virtual double potential(const LC * mol) {return 2;}
    virtual double potential(const Col * mol) {return 3;}
};
class Col : public Molecule
{
    public:
    /// more data and function should be here regarding the Col molecule
    virtual double potential(const Molecule * mol) {return 4;}
    virtual double potential(const LC * mol) {return 5;}
    virtual double potential(const Col * mol) {return 6;}
};

int main(int argc, char* argv[])
{
    Molecule * mol1 = new Col();
    Molecule * mol2 = new LC();

    double my_potential = mol1->potential(mol2);
    printf ("%f",my_potential);
}

Но я получаю результат 4, поскольку получается, что функция вызывается статически, а это означает, что, поскольку статический тип mol1 равен Molecule, вызывается функция:

virtual double potential(const Molecule * mol) {return 4;}

и не

virtual double potential(const LC * mol) {return 5;}

Есть ли способ вызывать функцию динамически (в ООП-дизайне) без использования в ней typeid?

полный ответ:

После исследования с советом Питера, это оказалось классической проблемой двойной отправки, полное решение состоит в том, чтобы просто вызвать другой виртуальный объект внутри виртуального, как это:

#include <iostream>
#include <typeinfo>
#include <stdio.h>


using namespace std;
class Col;
class LC;

class Molecule
{
    public:
    /// more data and functions should be here regarding the molecule
    double someBulshit;
    virtual double potential(const Molecule * mol) const = 0;
    virtual double potential(const Col * mol) const = 0;
    virtual double potential(const LC * mol) const = 0;
};

class LC : public Molecule
{
    public:
    /// more data and functions should be here regarding the LC molecule
    virtual double potential(const Molecule * mol) const {return mol->potential(this);}
    virtual double potential(const LC * mol) const {return 2;}
    virtual double potential(const Col * mol) const {return 3;}
};
class Col : public Molecule
{
    public:
    /// more data and function should be here regarding the Col molecule
    virtual double potential(const Molecule * mol) const {return mol->potential(this);}
    virtual double potential(const LC * mol) const {return 5;}
    virtual double potential(const Col * mol) const {return 6;}
};

int main(int argc, char* argv[])
{
    Molecule * mol1 = new Col();
    Molecule * mol2 = new LC();

    double my_potential = mol1->potential(mol2);
    printf ("%f",my_potential);
}

и это действительно возвращает 3 по желанию.

1 ответ

Решение

Вы всегда вызываете потенциал (Молекула *), так как это тип аргумента, который вы передаете.

Если вы хотите сделать это динамически (т.е. принять решение во время выполнения), вам нужно будет выполнить динамическое приведение, чтобы решить, какой тип у вас есть, и затем вызвать нужную функцию. Например, вы можете реализовать функцию с аргументом базового типа только в базовом классе, проверить фактический тип и затем вызвать перегруженную функцию с правильным типом. Код может выглядеть так:

#include <iostream>
#include <typeinfo>
#include <stdio.h>

using namespace std;
class Col;
class LC;

class Molecule
{
  public:
  virtual ~Molecule() {}
  /// more data and functions should be here regarding the molecule
  double someBulshit;
  double potential(const Molecule * mol);
  virtual double potential(const Col * mol) = 0;
  virtual double potential(const LC * mol) = 0;
};

class LC : public Molecule
{
  public:
  virtual ~LC() {}
  /// more data and functions should be here regarding the LC molecule
  virtual double potential(const LC * mol) {return 2;}
  virtual double potential(const Col * mol) {return 3;}
};
class Col : public Molecule
{
  public:
  virtual ~Col() {}
  /// more data and function should be here regarding the Col molecule
  virtual double potential(const LC * mol) {return 5;}
  virtual double potential(const Col * mol) {return 6;}
};

double Molecule::potential(const Molecule * mol) {
  const Col *mol_as_Col = dynamic_cast<const Col*>(mol);
  const LC *mol_as_LC = dynamic_cast<const LC*>(mol);
  if(mol_as_Col) {
    return potential(mol_as_Col);
  }
  else if(mol_as_LC) {
    return potential(mol_as_LC);
  }
  else {
    throw;
  }
}


int main(int argc, char* argv[])
{
  Molecule * mol1 = new Col();
  Molecule * mol2 = new LC();

  double my_potential = mol1->potential(mol2);
  printf ("%f",my_potential);
}

Результатом будет 5, а не 6, как вы написали в своем вопросе, поскольку вы вводите тип LC и вызываете Col. Я также добавил недостающие виртуальные деструкторы, так как полиморфные классы всегда должны иметь его.

В качестве альтернативы, вы можете попробовать реализовать это с помощью кода шаблона и вообще избегать использования полиморфизма во время выполнения (т.е. никогда не держите указатели типа Molecule*). Если вам интересно, я могу привести пример, но, поскольку вы специально запрашиваете динамическое решение, это не похоже на ответ на этот вопрос.

РЕДАКТИРОВАТЬ: Здесь следует демонстрация шаблона. Это не имеет смысла без контекста. Я добавил функцию printPotential(), которая демонстрирует, как вы будете писать остальную часть кода:

#include <iostream>
#include <typeinfo>
#include <stdio.h>

using namespace std;

template<typename MOLECULE1, typename MOLECULE2>
void printPotential(MOLECULE1 *mol1, MOLECULE2 *mol2) {
  double my_potential = mol1->potential(mol2);
  printf("%f",my_potential);
}

class Col;

class LC
{
  public:
  /// more data and functions should be here regarding the LC molecule
  double potential(const LC * mol) {return 2;}
  double potential(const Col * mol) {return 3;}
};
class Col
{
  public:
  /// more data and function should be here regarding the Col molecule
  double potential(const LC * mol) {return 5;}
  double potential(const Col * mol) {return 6;}
};


int main(int argc, char* argv[])
{
  Col *mol1 = new Col();
  LC *mol2 = new LC();

  printPotential(mol1, mol2);
}

На самом деле, когда я пишу это, я думаю, что может быть третий (и действительно предпочтительный) метод: вам нужно найти обобщенный алгоритм, как вычислять потенциал между двумя молекулами, используя только общую информацию, которая может быть частью базового класса (или получена). через виртуальные вызовы функций базового класса). В этом случае вы можете иметь одну единственную реализацию потенциал () (либо в качестве функции-члена базового класса с одним аргументом типа Molecule *, либо в качестве функции, не являющейся членом, с двумя аргументами типа Molecule*). Если я полагаю, что потенциал между двумя молекулами является разностью двух "абсолютных" потенциалов (просто как глупый пример), это может выглядеть так:

#include <iostream>
#include <typeinfo>
#include <stdio.h>

using namespace std;
class Col;
class LC;

class Molecule
{
  public:
  virtual ~Molecule() {}
  /// more data and functions should be here regarding the molecule
  double potential(const Molecule * mol) {
    return mol->getAbsolutePotential() - getAbsolutePotential();
  }
  virtual double getAbsolutePotential() const = 0;
};

class LC : public Molecule
{
  public:
  virtual ~LC() {}
  /// more data and functions should be here regarding the LC molecule
  double getAbsolutePotential() const {return 42.0;}
};
class Col : public Molecule
{
  public:
  virtual ~Col() {}
  /// more data and function should be here regarding the Col molecule
  double getAbsolutePotential() const {return 120.0;}
};


int main(int argc, char* argv[])
{
  Molecule * mol1 = new Col();
  Molecule * mol2 = new LC();

  double my_potential = mol1->potential(mol2);
  printf ("%f",my_potential);
}
Другие вопросы по тегам