使此代码正确多态
Making this code properly polymorphic
我有一个抽象的 class 父级,它有多个子级和用于与每个子级交互的空白函数。每个 Child 都会覆盖 Parent 的功能,并以不同的方式与其他 Childs 交互;即 Child1 对 interact_with(Child1)、interact_with(Child2) 等有不同的实现
在 Parent 中,我有一个函数 interact_with(Parent foo)。每个希望与另一个 Child 交互的 Child 都必须首先通过此功能。到目前为止一切都很好,但是我们 运行 遇到了一个问题:在处理了一些基本逻辑之后,Child 需要知道其参数的具体类型,以便它可以继续调用自己的重写函数。目前我有这个:
Child1* child1 = dynamic_cast<Child1*>(foo);
Child2* child2 = dynamic_cast<Child2*>(foo);
Child3* child3 = dynamic_cast<Child3*>(foo);
if(child1 != nullptr){
interact_with(child1)
}
else if(child2 != nullptr){
interact_with(child2)
}
else if(child3 != nullptr){
interact_with(child3)
}
它有效,但这不是一个很好的解决方案。当我有这么多 class 时,情况会变得特别糟糕。这是否表明基础设计有缺陷,如果是,我将如何改进它?
编辑:澄清一下:我有这样的东西
//Parent is an abstract class
class Parent
{
void interact_with(Parent* foo){
//this is here because there is a lengthy code segment
//that needs to be run no matter what child interacts
//with which
//afterwards, I need to interact with whatever foo really is
}
virtual void interact_with(Child1* child){*blank*};
virtual void interact_with(Child2* child){*blank*};
virtual void interact_with(Child3) child){*blank*};
};
class Child1 : public Parent
{
virtual void interact_with(Child1* child){*do something*};
virtual void interact_with(Child2* child){*do something else*};
virtual void interact_with(Child3* child){*do something else*};
};
已经。
像你说的那样使用 dynamic_cast<> 是糟糕的设计。您应该在声明中像这样使函数 interact_with 虚拟。
virtual void interact_with(Parent foo);
这将使方法调用使用子 class 的 interact_with 实现而不是父 class 的实现。然后你可以用这个替换你写的所有东西。
interact_with(foo);
Here is a pretty good explanation of virtual methods.
你要的是double dispatch
根据您的要求,您可以采用多种方法。一个非常通用的方法是使用 Key(type, type) -> Value(function) 形式的简单映射,将参数类型对与要调用的函数相关联。
在这种情况下,您将需要一组 void function(Parent*, Parent*)
类型的自由函数(一个用于您需要的每个组合)和一个 std::unordered_map<std::pair<TypeId, TypeId>, FunctionType>
类型的映射,其中 TypeId
是具有值语义的某种形式的类型标识符。
然后你在运行时进行调度:
if(map_.find(make_pair(type1, type2)) != map_.end())
map_[make_pair(type1, type2)](obj1, obj2);
不是在注册每个函数之前:
map_[make_pair(type1, type2)] = func12;
map_[make_pair(type2, type3)] = func23;
....
警告:此解决方案会破坏您的界面。不过别担心,它们已经坏了 ;)
在我看来,您的设计错误如下:尽管所有 children 都是 "equal",但您选择了其中一个负责交互并在其上调用一个方法(如果您想要 3, 4, ..., N等于children(在一个数组中)同时交互,谁负责?)
如果在您的应用程序中所有 object 都同等重要并且没有 object 负责交互,您应该将交互移动到自由重载的二进制函数中:
void interact(Child1* a, Child1* b);
void interact(Child1* a, Child2* b);
...
void interact(Child2* a, Child1* b)
{
interact(b, a); // if order does not matter, reuse another function
}
显然,它不会解决样板代码的问题,但至少可以帮助您 re-think 您的设计并找到比双重分派或转换更好的解决方案。
此外,根据函数内部结构,您可能可以通过使用模板函数而不是重载函数来轻松减少编写(但不是代码大小)。
使用双重分派的@imreal 答案是正确的。但是,可以使用虚拟成员函数而不是映射和函数指针(实际上类似于编译器生成的 vtable)来完成分派。
问题是单个虚函数无法解决问题,因为您确实需要双重分派(即关于两个对象的虚调用,而不仅仅是被调用的对象)。
请参阅以下工作示例:
#include <iostream>
class Child1;
class Child2;
class Parent
{
public:
virtual void interact_with(Parent* other) = 0;
virtual void interact_with(Child1* child) {};
virtual void interact_with(Child2* child) {};
};
class Child1 : public Parent
{
public:
virtual void interact_with(Parent* other)
{
other->interact_with(this);
}
virtual void interact_with(Child1* child)
{
std::cout << "Child1 - Child1\n";
}
virtual void interact_with(Child2* child)
{
std::cout << "Child1 - Child2\n";
}
};
class Child2 : public Parent
{
public:
virtual void interact_with(Parent* other)
{
other->interact_with(this);
}
virtual void interact_with(Child1* child)
{
std::cout << "Child2 - Child1\n";
}
virtual void interact_with(Child2* child)
{
std::cout << "Child2 - Child2\n";
}
};
int main()
{
Child1 c1;
Parent* p1 = &c1; // upcast to parent, from p1, we don't know the child type
Child2 c2;
Parent* p2 = &c2;
c1.interact_with(&c2); // single virtual call to Child1 - Child2
p1->interact_with(&c2); // single virtual call to Child1 - Child2
p1->interact_with(p2); // double virtual call to Child2 - Child1 (NB: reversed interaction)
}
它输出:
Child1 - Child2
Child1 - Child2
Child2 - Child1
注意最后一个是反的。那是因为要在参数上使用虚函数进行动态分派,我必须将 this
指针与参数交换。如果这些相互作用是对称的,这很好。如果不是,那么我建议围绕最通用的包装器创建一个包装器,再次交换 this
和参数。
我有一个抽象的 class 父级,它有多个子级和用于与每个子级交互的空白函数。每个 Child 都会覆盖 Parent 的功能,并以不同的方式与其他 Childs 交互;即 Child1 对 interact_with(Child1)、interact_with(Child2) 等有不同的实现
在 Parent 中,我有一个函数 interact_with(Parent foo)。每个希望与另一个 Child 交互的 Child 都必须首先通过此功能。到目前为止一切都很好,但是我们 运行 遇到了一个问题:在处理了一些基本逻辑之后,Child 需要知道其参数的具体类型,以便它可以继续调用自己的重写函数。目前我有这个:
Child1* child1 = dynamic_cast<Child1*>(foo);
Child2* child2 = dynamic_cast<Child2*>(foo);
Child3* child3 = dynamic_cast<Child3*>(foo);
if(child1 != nullptr){
interact_with(child1)
}
else if(child2 != nullptr){
interact_with(child2)
}
else if(child3 != nullptr){
interact_with(child3)
}
它有效,但这不是一个很好的解决方案。当我有这么多 class 时,情况会变得特别糟糕。这是否表明基础设计有缺陷,如果是,我将如何改进它?
编辑:澄清一下:我有这样的东西
//Parent is an abstract class
class Parent
{
void interact_with(Parent* foo){
//this is here because there is a lengthy code segment
//that needs to be run no matter what child interacts
//with which
//afterwards, I need to interact with whatever foo really is
}
virtual void interact_with(Child1* child){*blank*};
virtual void interact_with(Child2* child){*blank*};
virtual void interact_with(Child3) child){*blank*};
};
class Child1 : public Parent
{
virtual void interact_with(Child1* child){*do something*};
virtual void interact_with(Child2* child){*do something else*};
virtual void interact_with(Child3* child){*do something else*};
};
已经。
像你说的那样使用 dynamic_cast<> 是糟糕的设计。您应该在声明中像这样使函数 interact_with 虚拟。
virtual void interact_with(Parent foo);
这将使方法调用使用子 class 的 interact_with 实现而不是父 class 的实现。然后你可以用这个替换你写的所有东西。
interact_with(foo);
Here is a pretty good explanation of virtual methods.
你要的是double dispatch
根据您的要求,您可以采用多种方法。一个非常通用的方法是使用 Key(type, type) -> Value(function) 形式的简单映射,将参数类型对与要调用的函数相关联。
在这种情况下,您将需要一组 void function(Parent*, Parent*)
类型的自由函数(一个用于您需要的每个组合)和一个 std::unordered_map<std::pair<TypeId, TypeId>, FunctionType>
类型的映射,其中 TypeId
是具有值语义的某种形式的类型标识符。
然后你在运行时进行调度:
if(map_.find(make_pair(type1, type2)) != map_.end())
map_[make_pair(type1, type2)](obj1, obj2);
不是在注册每个函数之前:
map_[make_pair(type1, type2)] = func12;
map_[make_pair(type2, type3)] = func23;
....
警告:此解决方案会破坏您的界面。不过别担心,它们已经坏了 ;)
在我看来,您的设计错误如下:尽管所有 children 都是 "equal",但您选择了其中一个负责交互并在其上调用一个方法(如果您想要 3, 4, ..., N等于children(在一个数组中)同时交互,谁负责?)
如果在您的应用程序中所有 object 都同等重要并且没有 object 负责交互,您应该将交互移动到自由重载的二进制函数中:
void interact(Child1* a, Child1* b);
void interact(Child1* a, Child2* b);
...
void interact(Child2* a, Child1* b)
{
interact(b, a); // if order does not matter, reuse another function
}
显然,它不会解决样板代码的问题,但至少可以帮助您 re-think 您的设计并找到比双重分派或转换更好的解决方案。
此外,根据函数内部结构,您可能可以通过使用模板函数而不是重载函数来轻松减少编写(但不是代码大小)。
使用双重分派的@imreal 答案是正确的。但是,可以使用虚拟成员函数而不是映射和函数指针(实际上类似于编译器生成的 vtable)来完成分派。
问题是单个虚函数无法解决问题,因为您确实需要双重分派(即关于两个对象的虚调用,而不仅仅是被调用的对象)。
请参阅以下工作示例:
#include <iostream>
class Child1;
class Child2;
class Parent
{
public:
virtual void interact_with(Parent* other) = 0;
virtual void interact_with(Child1* child) {};
virtual void interact_with(Child2* child) {};
};
class Child1 : public Parent
{
public:
virtual void interact_with(Parent* other)
{
other->interact_with(this);
}
virtual void interact_with(Child1* child)
{
std::cout << "Child1 - Child1\n";
}
virtual void interact_with(Child2* child)
{
std::cout << "Child1 - Child2\n";
}
};
class Child2 : public Parent
{
public:
virtual void interact_with(Parent* other)
{
other->interact_with(this);
}
virtual void interact_with(Child1* child)
{
std::cout << "Child2 - Child1\n";
}
virtual void interact_with(Child2* child)
{
std::cout << "Child2 - Child2\n";
}
};
int main()
{
Child1 c1;
Parent* p1 = &c1; // upcast to parent, from p1, we don't know the child type
Child2 c2;
Parent* p2 = &c2;
c1.interact_with(&c2); // single virtual call to Child1 - Child2
p1->interact_with(&c2); // single virtual call to Child1 - Child2
p1->interact_with(p2); // double virtual call to Child2 - Child1 (NB: reversed interaction)
}
它输出:
Child1 - Child2
Child1 - Child2
Child2 - Child1
注意最后一个是反的。那是因为要在参数上使用虚函数进行动态分派,我必须将 this
指针与参数交换。如果这些相互作用是对称的,这很好。如果不是,那么我建议围绕最通用的包装器创建一个包装器,再次交换 this
和参数。