事先不知道所有的类,如何实现双重调度呢?
How can I implement double dispatch when I don't know all the classes in advance?
我有一个基础 class,其中(可能)有很多子class,我希望能够比较基础 class 的任意两个对象] 为了平等。我正在尝试在不调用亵渎神明的 typeid 关键字的情况下执行此操作。
#include <iostream>
struct Base {
virtual bool operator==(const Base& rhs) const
{return rhs.equalityBounce(this);}
virtual bool equalityBounce(const Base* lhs) const = 0;
virtual bool equalityCheck(const Base* lhs) const = 0;
};
struct A : public Base {
A(int eh) : a(eh) {}
int a;
virtual bool equalityBounce(const Base* lhs) const{
return lhs->equalityCheck(this);
}
virtual bool equalityCheck(const Base* rhs) const {return false;}
virtual bool equalityCheck(const A* rhs) const {return a == rhs->a;}
};
int main() {
Base *w = new A(1), *x = new A(2);
std::cout << (*w == *w) << "\n";
std::cout << (*w == *x) << "\n";
}
我知道所写代码失败是因为 equalityBounce() 中的 lhs 是一个 Base*,所以它甚至不知道采用 A* 的 equalityCheck() 版本。但是我不知道该怎么办。
如果您需要虚拟等于运算符并且想要避免 dynamic_cast 您可以将枚举类型添加到指示类型的基础 class 并创建虚拟 getter 和如果匹配则进行静态转换。
enum ClassType { AType, BType };
Class A
{
public:
virtual ClassType getType ();
virtual bool operator ==(const A& a ) const;
};
// in B implementation
virtual bool B::operator ==(const A& a)
{
if (!A::operator==(a)) return false;
if(a.getType () != ClassType::BType) return false;
const B& b = static_cast <const& B>(a);
// do B == checks
return true;
}
为什么不起作用
您的双重分派实现的问题是您希望调用最具体的 equalityCheck()
。
但是您的实现完全基于多态基础 class 和 equalityCheck(const A*)
重载,但 不会覆盖 equalityCheck(const Base*)
!
换句话说,在编译时编译器知道 A::equalityBounce()
可以调用 equalityCheck(A*)
(因为 this
是一个 A*
),但不幸的是它调用 Base::equalityCheck()
没有针对 A*
参数的专门版本。
如何实现?
要使双重分派工作,您需要在您的基础 class 中具有双重分派 equalityCheck() 的特定类型实现。
为此,Base 需要知道它的后代:
struct A;
struct Base {
virtual bool operator==(const Base& rhs) const
{
return rhs.equalityBounce(this);
}
virtual bool equalityBounce(const Base* lhs) const = 0;
virtual bool equalityCheck(const Base* lhs) const = 0;
virtual bool equalityCheck(const A* lhs) const = 0;
};
struct A : public Base {
...
bool equalityBounce(const Base* lhs) const override{
return lhs->equalityCheck(this);
}
bool equalityCheck(const Base* rhs) const override {
return false;
}
bool equalityCheck(const A* rhs) const override{
return a == rhs->a;
}
};
注意使用 override
以确保该函数确实覆盖了基类的虚函数。
有了这个实现,它将起作用,因为:
A::equalityBounce()
会调用 Base::equalityCheck()
- 在此函数的所有重载版本中,它将选择
Base::equalityCheck(A*)
,因为 this
是一个 A*
- 调用的
Base *lhs
对象将调用它的 equalityCheck(A*)
。如果 lhs
是一个 A*
它将因此去 A::equalityCheck(A*)
这将产生预期的(正确的)结果。 恭喜!
- 假设
lhs
将是指向另一个 class X
的指针,该 X
也派生自 Base
。在这种情况下,lhs->equalityCheck(A*)
会调用 X::equalityCheck(A*)
并且也可能 return 正确的响应,考虑到您将 X
与 A
进行比较。
如何使其可扩展?双调度图 !
使用强类型语言进行双重分派的问题在于,"bounced" 对象需要知道如何与特定的(事先已知的)classes 进行比较。由于源对象和反弹对象属于相同的多态基类型,因此基需要知道所有涉及的类型。这种设计严重限制了可扩展性。
如果你想在基础 class 中添加任何派生类型而事先不知道它,那么你必须通过动态类型(无论是 dynamic_cast 还是 typeid):
我在这里向您提出动态可扩展性的建议。它使用单分派来比较两个相同类型的对象,并使用 双分派映射 来比较它们之间的不同类型(如果没有声明,return 默认为 false) :
struct Base {
typedef bool(*fcmp)(const Base*, const Base*); // comparison function
static unordered_map < type_index, unordered_map < type_index, fcmp>> tcmp; // double dispatch map
virtual bool operator==(const Base& rhs) const
{
if (typeid(*this) == typeid(rhs)) { // if same type,
return equalityStrict(&rhs); // use a signle dispatch
}
else { // else use dispatch map.
auto i = tcmp.find(typeid(*this));
if (i == tcmp.end() )
return false; // if nothing specific was foreseen...
else {
auto j = i->second.find(typeid(rhs));
return j == i->second.end() ? false : (j->second)(this, &rhs);
}
}
}
virtual bool equalityStrict(const Base* rhs) const = 0; // for comparing two objects of the same type
};
A class 将被重写为:
struct A : public Base {
A(int eh) : a(eh) {}
int a;
bool equalityStrict(const Base* rhs) const override { // how to compare for the same type
return (a == dynamic_cast<const A*>(rhs)->a);
}
};
使用此代码,您可以将任何对象与同一类型的对象进行比较。现在为了显示可扩展性,我创建了一个 struct X
,其成员与 A
相同。如果我想允许 A 与 X 比较,我只需要定义一个比较函数:
bool iseq_X_A(const Base*x, const Base*a) {
return (dynamic_cast<const X*>(x)->a == dynamic_cast<const A*>(a)->a);
} // not a member function, but a friend.
然后为了使动态双分派工作,我必须将这个函数添加到双分派映射中:
Base::tcmp[typeid(X)][typeid(A)] = iseq_X_A;
那么结果很容易验证:
Base *w = new A(1), *x = new A(2), *y = new X(2);
std::cout << (*w == *w) << "\n"; // true returned by A::equalityStrict
std::cout << (*w == *x) << "\n"; // false returned by A::equalityStrict
std::cout << (*y == *x) << "\n"; // true returned by isseq_X_A
由于dynamic_cast
,虽然速度很慢,但我认为最合适的解决方案是下一个:
#include <iostream>
struct Base {
virtual bool operator==(const Base& rhs) const
{ return equalityBounce(&rhs); }
virtual bool equalityBounce(const Base* lhs) const = 0;
};
template<typename Derived>
struct BaseHelper : public Base
{
bool equalityBounce(const Base* rhs) const
{
const Derived* p_rhs = dynamic_cast<const Derived*>(rhs);
if (p_rhs == nullptr)
return false;
else
return p_rhs->equalityCheck
(reinterpeter_cast<const Derived*>(this));
}
virtual bool equalityCheck(const Derived*) const = 0;
};
struct A : public BaseHelper<A> {
A(int eh) : a(eh) {}
int a;
virtual bool equalityCheck(const A* rhs) const
{ return a == rhs->a; }
};
int main() {
Base *w = new A(1), *x = new A(2);
std::cout << (*w == *w) << "\n"; // Prints 1.
std::cout << (*w == *x) << "\n"; // Prints 0.
}
这样,您只需关心比较与 "me" 具有相同派生类型的对象。您不需要编写更多重载,因为 BaseHelper 会为您完成所有辅助工作。
我有一个基础 class,其中(可能)有很多子class,我希望能够比较基础 class 的任意两个对象] 为了平等。我正在尝试在不调用亵渎神明的 typeid 关键字的情况下执行此操作。
#include <iostream>
struct Base {
virtual bool operator==(const Base& rhs) const
{return rhs.equalityBounce(this);}
virtual bool equalityBounce(const Base* lhs) const = 0;
virtual bool equalityCheck(const Base* lhs) const = 0;
};
struct A : public Base {
A(int eh) : a(eh) {}
int a;
virtual bool equalityBounce(const Base* lhs) const{
return lhs->equalityCheck(this);
}
virtual bool equalityCheck(const Base* rhs) const {return false;}
virtual bool equalityCheck(const A* rhs) const {return a == rhs->a;}
};
int main() {
Base *w = new A(1), *x = new A(2);
std::cout << (*w == *w) << "\n";
std::cout << (*w == *x) << "\n";
}
我知道所写代码失败是因为 equalityBounce() 中的 lhs 是一个 Base*,所以它甚至不知道采用 A* 的 equalityCheck() 版本。但是我不知道该怎么办。
如果您需要虚拟等于运算符并且想要避免 dynamic_cast 您可以将枚举类型添加到指示类型的基础 class 并创建虚拟 getter 和如果匹配则进行静态转换。
enum ClassType { AType, BType };
Class A
{
public:
virtual ClassType getType ();
virtual bool operator ==(const A& a ) const;
};
// in B implementation
virtual bool B::operator ==(const A& a)
{
if (!A::operator==(a)) return false;
if(a.getType () != ClassType::BType) return false;
const B& b = static_cast <const& B>(a);
// do B == checks
return true;
}
为什么不起作用
您的双重分派实现的问题是您希望调用最具体的 equalityCheck()
。
但是您的实现完全基于多态基础 class 和 equalityCheck(const A*)
重载,但 不会覆盖 equalityCheck(const Base*)
!
换句话说,在编译时编译器知道 A::equalityBounce()
可以调用 equalityCheck(A*)
(因为 this
是一个 A*
),但不幸的是它调用 Base::equalityCheck()
没有针对 A*
参数的专门版本。
如何实现?
要使双重分派工作,您需要在您的基础 class 中具有双重分派 equalityCheck() 的特定类型实现。
为此,Base 需要知道它的后代:
struct A;
struct Base {
virtual bool operator==(const Base& rhs) const
{
return rhs.equalityBounce(this);
}
virtual bool equalityBounce(const Base* lhs) const = 0;
virtual bool equalityCheck(const Base* lhs) const = 0;
virtual bool equalityCheck(const A* lhs) const = 0;
};
struct A : public Base {
...
bool equalityBounce(const Base* lhs) const override{
return lhs->equalityCheck(this);
}
bool equalityCheck(const Base* rhs) const override {
return false;
}
bool equalityCheck(const A* rhs) const override{
return a == rhs->a;
}
};
注意使用 override
以确保该函数确实覆盖了基类的虚函数。
有了这个实现,它将起作用,因为:
A::equalityBounce()
会调用Base::equalityCheck()
- 在此函数的所有重载版本中,它将选择
Base::equalityCheck(A*)
,因为this
是一个A*
- 调用的
Base *lhs
对象将调用它的equalityCheck(A*)
。如果lhs
是一个A*
它将因此去A::equalityCheck(A*)
这将产生预期的(正确的)结果。 恭喜! - 假设
lhs
将是指向另一个 classX
的指针,该X
也派生自Base
。在这种情况下,lhs->equalityCheck(A*)
会调用X::equalityCheck(A*)
并且也可能 return 正确的响应,考虑到您将X
与A
进行比较。
如何使其可扩展?双调度图 !
使用强类型语言进行双重分派的问题在于,"bounced" 对象需要知道如何与特定的(事先已知的)classes 进行比较。由于源对象和反弹对象属于相同的多态基类型,因此基需要知道所有涉及的类型。这种设计严重限制了可扩展性。
如果你想在基础 class 中添加任何派生类型而事先不知道它,那么你必须通过动态类型(无论是 dynamic_cast 还是 typeid):
我在这里向您提出动态可扩展性的建议。它使用单分派来比较两个相同类型的对象,并使用 双分派映射 来比较它们之间的不同类型(如果没有声明,return 默认为 false) :
struct Base {
typedef bool(*fcmp)(const Base*, const Base*); // comparison function
static unordered_map < type_index, unordered_map < type_index, fcmp>> tcmp; // double dispatch map
virtual bool operator==(const Base& rhs) const
{
if (typeid(*this) == typeid(rhs)) { // if same type,
return equalityStrict(&rhs); // use a signle dispatch
}
else { // else use dispatch map.
auto i = tcmp.find(typeid(*this));
if (i == tcmp.end() )
return false; // if nothing specific was foreseen...
else {
auto j = i->second.find(typeid(rhs));
return j == i->second.end() ? false : (j->second)(this, &rhs);
}
}
}
virtual bool equalityStrict(const Base* rhs) const = 0; // for comparing two objects of the same type
};
A class 将被重写为:
struct A : public Base {
A(int eh) : a(eh) {}
int a;
bool equalityStrict(const Base* rhs) const override { // how to compare for the same type
return (a == dynamic_cast<const A*>(rhs)->a);
}
};
使用此代码,您可以将任何对象与同一类型的对象进行比较。现在为了显示可扩展性,我创建了一个 struct X
,其成员与 A
相同。如果我想允许 A 与 X 比较,我只需要定义一个比较函数:
bool iseq_X_A(const Base*x, const Base*a) {
return (dynamic_cast<const X*>(x)->a == dynamic_cast<const A*>(a)->a);
} // not a member function, but a friend.
然后为了使动态双分派工作,我必须将这个函数添加到双分派映射中:
Base::tcmp[typeid(X)][typeid(A)] = iseq_X_A;
那么结果很容易验证:
Base *w = new A(1), *x = new A(2), *y = new X(2);
std::cout << (*w == *w) << "\n"; // true returned by A::equalityStrict
std::cout << (*w == *x) << "\n"; // false returned by A::equalityStrict
std::cout << (*y == *x) << "\n"; // true returned by isseq_X_A
由于dynamic_cast
,虽然速度很慢,但我认为最合适的解决方案是下一个:
#include <iostream>
struct Base {
virtual bool operator==(const Base& rhs) const
{ return equalityBounce(&rhs); }
virtual bool equalityBounce(const Base* lhs) const = 0;
};
template<typename Derived>
struct BaseHelper : public Base
{
bool equalityBounce(const Base* rhs) const
{
const Derived* p_rhs = dynamic_cast<const Derived*>(rhs);
if (p_rhs == nullptr)
return false;
else
return p_rhs->equalityCheck
(reinterpeter_cast<const Derived*>(this));
}
virtual bool equalityCheck(const Derived*) const = 0;
};
struct A : public BaseHelper<A> {
A(int eh) : a(eh) {}
int a;
virtual bool equalityCheck(const A* rhs) const
{ return a == rhs->a; }
};
int main() {
Base *w = new A(1), *x = new A(2);
std::cout << (*w == *w) << "\n"; // Prints 1.
std::cout << (*w == *x) << "\n"; // Prints 0.
}
这样,您只需关心比较与 "me" 具有相同派生类型的对象。您不需要编写更多重载,因为 BaseHelper 会为您完成所有辅助工作。