c++:根据参数类型调用派生类的正确方法

C++ : calling the right method of a derived class according to the types of the arguments

本文关键字:方法 派生 调用 参数 类型 c++      更新时间:2023-10-16

假设有一个基类和它的两个派生类;基类拥有一个execute方法,每个派生类实现该方法的不同版本,具有不同类型和数量的参数;我不能使用虚方法,因为每个派生类的签名应该完全相同;我的目标是提供一个基本的execute方法,它可以接受任何类型的参数,推断它们的类型,并将它们分派给正确的派生类中的正确方法;我看了一下访问者模式,但我正在寻找一个更灵活、更优雅的解决方案;

edit:我想把这些类存储在一个向量中,所以我需要一个基类

这是我在gcc 4.5:

下的尝试(我不知道在base execute的主体中放入什么)
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)
}

在基类和派生类之间使用了一个中间类:

#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)
}

基类中不能有任意(=  unlimited)数量的虚函数。您必须决定哪些函数应该可用并声明它们。否则你就不需要虚函数了,你只需要做一些编译时分派,也许只需要像这样通过重载解析:

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)); }

编译时还是运行时?

您需要知道是否可以在编译时决定要调用哪个方法。如果你想在运行时决定,那就叫做多重分派,在c++中没有内置的、简短的解决方案(参见c++中的多重分派问题)。你可以用访问者模式或双重调度来模拟它。这是Bjarne Stroustroup等人写的一篇关于实现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。对于特定的情况,可以对该模板函数进行专门化。

如果我正确理解了您想要完成的任务,您可能会发现查看"双重分派"模式很有用:

双调度是多调度的一种特殊形式,是一种根据调用中涉及的两个对象的运行时类型(源)将函数调用分派到不同具体函数的机制

粗略地说:你的客户端对象在目标对象上调用"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;
}