c++:根据参数类型调用派生类的正确方法
C++ : calling the right method of a derived class according to the types of the arguments
假设有一个基类和它的两个派生类;基类拥有一个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;
}
- 有没有一种"cleaner"的方法可以在指向基的指针向量中找到派生类的第一个实例?
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 为什么此派生对象无法访问基类的后递减方法?
- 是否可以为 QPixmap 派生类嵌入缩放方法?
- 绑定派生类方法C++从实例范围之外的分隔 std::function 变量调用
- 使用基类中的派生方法运行线程,而无需使用模板
- 是否可以使用基类非虚拟方法中的派生类虚拟方法?
- 在派生类中使用基类的私有成员变量的最佳方法
- 如何在从抽象基派生的类中实现相同的方法?
- 调用从模板派生的类的静态方法,而不指定模板
- 如何在工厂方法中返回指向基于基础操作系统的派生类的有效指针
- 如何将成员函数作为参数传递并在派生对象上执行方法列表
- 从基类实例调用派生类方法而不进行强制转换
- 派生类调用父类的方法,该方法调用重写的虚拟方法调用错误的方法
- 从纯虚拟类 (A) 派生的指针无法访问来自纯类 (B) 的重载方法
- 通过基类接受方法转发派生 UniquePtr 的右值会移动引用而不是复制
- C ++基础私有方法在将自身转换为派生类后可以访问吗?
- C++ 使用派生类方法更改基类数据成员
- 如何使用派生类特定的方法:派生类中的派生成员
- 使用抽象类的指针访问派生类的方法;派生类似乎也是抽象的