假设我有 abstract base class Shape
, 与派生类 Circle
和 Rectangle
.
class Shape {};
class Circle : public Shape {};
class Rectangle : public Shape {};
我需要确定两个形状是否相等,假设我有两个 Shape*
指针。 (这是因为我有两个 vector<Shape*>
的实例,我想看看它们是否具有相同的形状。)
推荐的方法是 double dispatch .我想出的是这个(这里大大简化了,所以形状等于所有其他相同类型的形状):
class Shape {
public:
virtual bool equals(Shape* other_shape) = 0;
protected:
virtual bool is_equal(Circle& circle) { return false; };
virtual bool is_equal(Rectangle& rect) { return false; };
friend class Circle; // so Rectangle::equals can access Circle::is_equal
friend class Rectangle; // and vice versa
};
class Circle : public Shape {
public:
virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this); };
protected:
virtual bool is_equal(Circle& circle) { return true; };
};
class Rectangle : public Shape {
public:
virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this); };
protected:
virtual bool is_equal(Rectangle& circle) { return true; };
};
这行得通,但我必须添加一个单独的 equals
功能和friend
申报 Shape
对于每个派生类。然后我必须复制粘贴 完全相同的 equals
function 也进入每个派生类。这是一个非常多的样板,比如说 10 种不同的形状!
有更简单的方法吗?
dynamic_cast
是不可能的;太慢了。 (是的,我对其进行了基准测试。速度对我的应用程序很重要。)
我试过了,但没用:
class Shape {
public:
virtual bool equals(Shape* other_shape) = 0;
private:
virtual bool is_equal(Shape& circle) { return false; };
};
class Circle : public Shape {
public:
virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this); };
private:
virtual bool is_equal(Circle& circle) { return true; };
};
class Rectangle : public Shape {
public:
virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this); };
private:
virtual bool is_equal(Rectangle& circle) { return true; };
};
equals()
始终返回 false,即使在相同的形状上也是如此。似乎调度总是选择 is_equal(Shape&)
基本功能,即使“更具体”的匹配可用。这可能是有道理的,但我对 C++ 调度的理解不够透彻,不知道为什么。
最佳答案
当您创建这样的方法时:
virtual bool is_equal(Shape& circle) { return false; };
在子类中,
virtual bool is_equal(Circle& circle) { return true; };
这些不是同一种方法。您有两个单独的虚拟方法,它们都没有被覆盖(正如 Ben Voigt 指出的那样,它们重载甚至没有重载)。当您调用 Shape::is_equal
时,只有一个版本:Shape::is_equal(Shape&)
... 不会被覆盖并始终返回 false。
您必须在父类中定义单独的重载方法,然后在子类中覆盖它们。例如,
class Shape {
// Choice between these two methods happens at compile time...
virtual bool is_equal(Circle& circle) { return false; };
virtual bool is_equal(Rectangle& circle) { return false; };
};
class Rectangle : Shape {
// Choice between this and Shape::is_equal(Rectangle&) happens at runtime...
virtual bool is_equal(Rectangle& circle) { return true; };
};
但是,使用这样的技巧,您可能无法达到 C 程序员执行此操作的方式的性能或简单性:
typedef enum {
SHAPE_CIRCLE,
SHAPE_RECTANGLE
} shape_type_t;
struct shape {
shape_type_t type;
};
struct circle {
shape_type_t type;
...
};
struct rectangle {
shape_type_t type;
...
};
bool shape_equal(struct shape *x, struct shape *y)
{
if (x->type != y->type)
return false;
switch (x->type) {
case SHAPE_CIRCLE:
return circle_equal((struct circle *) x, (struct circle *) y);
case SHAPE_RECTANGLE:
...;
}
}
如果重载和虚方法使您的代码比 C 版本更复杂,那么您可能希望重新考虑是否使用重载和虚方法解决了这个特定问题。
关于Equals() 的 C++ 双重分派(dispatch),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7393458/