C ++: вызов правильного метода производного класса в соответствии с типами аргументов - PullRequest
5 голосов
/ 13 ноября 2011

Допустим, у нас есть базовый класс и два его производных класса; Базовый класс владеет методом execute, и каждый производный класс реализует свою версию этого метода с разными типами и количеством аргументов; Я не могу использовать виртуальный метод, потому что подпись должна быть точно такой же для каждого производного класса; Моя цель - предложить базовый метод execute, который принимает аргументы любого типа, выводит их типы и отправляет их нужному методу в правильном производном классе; Я взглянул на шаблон Visitor, но я ищу более гибкое и элегантное решение;

edit : я хочу сохранить эти классы в векторе, поэтому мне нужен базовый класс

Вот моя попытка (я не знаю, что поместить в тело базового исполнения) под gcc 4.5 :

class Base {

  public:

  Base();
  ~Base();

  template<typename ...Args>
  void execute(Args... arg)
  {
    //calls the right method
    //execute(int i) or execute(int i, float f)
    //as Args are int or int and float
  }

};

class DerivedA : public Base
{

  public:

  DerivedA();
  ~DerivedA();

  void execute(int i){ /*do something with i*/}

};

class DerivedB : public Base
{

  public:

  DerivedB();
  ~DerivedB();

  void execute(int i, float f){/*do something with i and f*/}

};

void test()
{
  Base* b1 = new DerivedA();
  Base* b2 = new DerivedB();

  int i = 5;
  b1->execute(i); //should call DerivedA.execute(int i)
  float f = 5.0f;
  b2->execute(i, f); //should call DerivedB.execute(int i, float f)

}

Ответы [ 4 ]

5 голосов
/ 13 ноября 2011

Далее используется промежуточный класс между базовым и производным классом:

#include <utility>
#include <iostream>
#include <stdexcept>

template<typename... Args> class Intermediate;

class Base
{
public:
  virtual ~Base() {}

  template<typename ...Args>
  void execute(Args... args)
  {
    typedef Intermediate<Args...>* pim;
    if (pim p = dynamic_cast<pim>(this))
    {
      p->execute(std::forward<Args>(args)...);
    }
    else
    {
      throw std::runtime_error("no suitable derived class");
    }
  }
};

template<typename... Args> class Intermediate:
  public Base
{
public:
  virtual void execute(Args ... arg) = 0;
};

class DerivedA:
  public Intermediate<int>
{
public:
  void execute(int i)
  {
    std::cout << "DerivedA: i = " << i << "\n";
  }
};

class DerivedB:
  public Intermediate<int, float>
{
public:
  void execute(int i, float f)
  {
    std::cout << "DerivedB: i = " << i << ", f = " << f << "\n";
  }
};

int main()
{
  Base* b1 = new DerivedA();
  Base* b2 = new DerivedB();

  int i = 5;
  b1->execute(i); //should call DerivedA.execute(int i)
  float f = 5.0f;
  b2->execute(i, f); //should call DerivedB.execute(int i, float f)
}
1 голос
/ 13 ноября 2011

Время компиляции или время выполнения?

Вам нужно знать, можете ли вы решить во время компиляции, какой метод вы хотите вызвать. Если вы хотите принять решение во время выполнения, то это называется множественной диспетчеризацией, и для C ++ нет встроенного краткого решения (см. Также вопрос Многократная диспетчеризация в C ++ ). , Вы можете сортировать его по шаблону Visitor или двойной диспетчеризации. Вот статья о реализации поддержки мультиметодов для компилятора C ++ Бьярном Страуструпом и другими.

Реализация времени компиляции с бесплатной функцией

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

#include <iostream>

class DerivedA //: public Base
{
public:
    void execute(int i)
    { 
        std::cout << "I'm DerivedA::execute(int)! " << std::endl; 
    }
};

class DerivedB //: public Base
{
public:
    void execute(int i, float f) 
    {
        std::cout << "I'm DerivedB::execute(int, float)! " << std::endl; 
    }
};

template<typename Class, typename... Args>
void execInvoker(Class* obj, Args... args)
{
    static_cast<Class*>(obj)->execute(std::forward<Args>(args)...);
}

int main(int argc, char* argv[])
{
    DerivedA a;
    DerivedB b;

    int i = 5;
    float f = 5.2f;
    execInvoker(&a, i);
    execInvoker(&b, i, f);
}

Вы получите ошибки компиляции, если попытаетесь вызвать несуществующий метод execute (неправильные типы или неправильное количество аргументов). Я протестировал приведенный выше код с g ++ 4.6, и результат будет ожидаемым:

$ g++ -std=c++0x -Wall variadic.cpp 
$ ./a.out 
I'm DerivedA::execute(int)! 
I'm DerivedB::execute(int, float)!

Аналогичный подход без свободной функции

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

template<typename Class>
class Proxy
{
private:
    Class* obj;

public:
    Proxy(Class* _obj) : obj(_obj) {}

    template<typename... Args>
    void execute(Args... args)
    {
        obj->execute(std::forward<Args>(args)...);
    }
};

Это позволяет следующий код:

Proxy<DerivedA> proxy(&a);
proxy.execute(i);

Очевидным преимуществом этого подхода является то, что вы можете передать этот прокси-объект в функции шаблона, такие как эта:

template<typename Class>
void proxyUser(Proxy<Class>& p)
{
    p.execute(4, 0.3f);
}

И это назовет правильный execute. Для особых случаев вы можете специализировать эту функцию шаблона.

1 голос
/ 13 ноября 2011

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

struct Base
{
   void foo(int a)          { dynamic_cast<DerA*>(this)->fooimpl(a); }
   void foo(int a, float b) { dynamic_cast<DerB*>(this)->fooimpl(a, b); }
   void foo(bool a, char b) { dynamic_cast<DerC*>(this)->fooimpl(a, b); }

   virtual ~Base() { }  // dynamic cast requires polymorphic class
};

Конечно, вы должны добавить проверку на допустимость:

if (DerA * p = dynamic_cast<DerA*>(this)) { p->fooimpl(a)); }
0 голосов
/ 13 ноября 2011

Если я правильно понимаю, что вы пытаетесь достичь, вам может быть полезно взглянуть на шаблон «двойной отправки»:

двойная диспетчеризация - это особая форма множественной диспетчеризации и механизм, который отправляет вызов функции различным конкретным функциям в зависимости от типов времени выполнения двух объектов, участвующих в вызове ( source )

Грубо: ваш клиентский объект вызывает "execute" для целевого объекта:

target.execute(client);

целевой объект вызывает метод для промежуточного объекта, который действует как расширенная виртуальная таблица (фактически, таблица с несколькими отправлениями):

dispatchTable.execute(client, *this);  //-- target calls this

и таблица отправки по очереди вызывает правильный метод (с полной подписью) для целевого объекта:

<get arguments from client>
target.specific_execute(arguments)

В качестве альтернативы и, возможно, более удобно, механизм таблицы диспетчеризации может быть предложен самим клиентским объектом. Итак, target::execute звонит:

client.execute(target);

и client::execute(target) наконец вызовут:

target.specific_execute(args);

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

Это может потребовать некоторого рефакторинга вашего проекта (при первом подходе клиент должен предложить способ получения аргументов для вызова), и это может выглядеть как реализация довольно низкого уровня (таблица диспетчеризации), но это чистый подход, ИМО.

class Client;
struct Base {
    virtual void dispatch(Client& c);
    void execute(Base& b) {
        std::cout << "void execute(Base&)" << std::endl;
    }
};

struct DerivedA : public Base {
    void exec(int i){ 
        /*do something with i*/
        std::cout << "void execute(int i)" << std::endl;
    }
};

struct DerivedB : public Base {
    void exec(int i, float f)
    {
        std::cout << "void execute(int i, float f)" << std::endl;
    }
};

struct Client {
    int i;
    float f;

    void execute(Base& obj) {
    }
    void execute(DerivedA& obj) {
        obj.exec(i);
    }
    void execute(DerivedB& obj) {
        obj.exec(i, f);
    }
    void doTest() {
        Base* b1 = new DerivedA();
        Base* b2 = new DerivedB();
        b1->dispatch(*this);
        b2->dispatch(*this);
    }
};

void Base::dispatch(Client& c) {
    c.execute(*this);
}
void DerivedA::dispatch(Client& c) {
    c.execute(*this);
}
void DerivedB::dispatch(Client& c) {
    c.execute(*this);
}

int main (int argc, char * const argv[]) {
    // insert code here...
    std::cout << "Hello, World!\n";

    Client c;
    c.doTest();

    return 0;
}
...