C++ 双调度 Equals()
假设我有 抽象基类 Shape代码>,带有派生类
Circle
和Rectangle
.
class Shape {};类圆:公共形状{};类矩形:公共形状{};
我需要确定两个形状是否相等,假设我有两个 Shape*
指针.(这是因为我有两个 vector
实例,我想看看它们是否具有相同的形状.)
推荐的方法是双重调度.我想出的是这个(这里大大简化,所以形状等于所有其他相同类型的形状):
类形状{上市:虚拟布尔等于(形状*其他形状)= 0;受保护:virtual bool is_equal(Circle& circle) { return false;};virtual bool is_equal(Rectangle& rect) { return false;};朋友类圈子;//所以 Rectangle::equals 可以访问 Circle::is_equal朋友类矩形;//反之亦然};类圆:公共形状{上市:virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this);};受保护:virtual bool is_equal(Circle& circle) { return true;};};类矩形:公共形状{上市:virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this);};受保护:virtual bool is_equal(Rectangle& circle) { return true;};};
这有效,但我必须为每个派生类在 Shape
中添加单独的 equals
函数和 friend
声明.然后我也必须将完全相同的equals
函数复制粘贴到每个派生类中.比如说有 10 种不同的形状,这是一大堆样板!
有更简单的方法吗?
dynamic_cast
是不可能的;太慢了.(是的,我对其进行了基准测试.速度在我的应用中很重要.)
我试过了,但没有用:
类形状{上市:虚拟布尔等于(形状*其他形状)= 0;私人的:virtual bool is_equal(Shape& circle) { return false;};};类圆:公共形状{上市:virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this);};私人的:virtual bool is_equal(Circle& circle) { return true;};};类矩形:公共形状{上市:virtual bool equals(Shape* other_shape) { return other_shape->is_equal(*this);};私人的:virtual bool is_equal(Rectangle& circle) { return true;};};
equals()
总是返回 false,即使在相同的形状上也是如此.似乎 dispatch 总是选择 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.>
您必须在父类中定义单独的重载方法,然后在子类中覆盖它们.例如,
类形状{//这两种方法之间的选择发生在编译时...virtual bool is_equal(Circle& circle) { return false;};virtual bool is_equal(Rectangle& circle) { return false;};};类矩形:形状{//this 和 Shape::is_equal(Rectangle&) 之间的选择发生在运行时...virtual bool is_equal(Rectangle& circle) { return true;};};
但是,使用这样的技巧,您可能无法像 C 程序员那样达到性能或简单性:
typedef enum {SHAPE_CIRCLE,SHAPE_RECTANGLE} shape_type_t;结构形状{shape_type_t 类型;};结构圈{shape_type_t 类型;...};结构矩形{shape_type_t 类型;...};bool shape_equal(结构形状*x,结构形状*y){如果 (x-> 类型 != y-> 类型)返回假;开关(x->类型){案例 SHAPE_CIRCLE:return circle_equal((struct circle *) x, (struct circle *) y);案例 SHAPE_RECTANGLE:...;}}
如果重载和虚方法使您的代码比 C 版本更复杂,那么您可能希望重新考虑是否使用重载和虚方法解决了这个特定问题.
Imagine I have abstract base class Shape
, with derived classes Circle
and Rectangle
.
class Shape {};
class Circle : public Shape {};
class Rectangle : public Shape {};
I need to determine if two shapes are equal, assuming I have two Shape*
pointers. (This is because I have two instances of vector<Shape*>
and I want to see if they have the same shapes.)
The recommended way to do this is double dispatch. What I've come up with is this (greatly simplified here, so that shapes are equal to all other shapes of the same type):
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; };
};
This works, but I have to add a separate equals
function and friend
declaration in Shape
for each derived class. Then I have to copy-paste the exact same equals
function into each derived class, too. This is an awful lot of boilerplate for say, 10 different shapes!
Is there a simpler way to do it?
dynamic_cast
is out of the question; too slow. (Yes, I benchmarked it. Speed matters in my app.)
I tried this but it doesn't work:
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()
always returns false, even on identical shapes. It seems dispatch is always choosing the is_equal(Shape&)
base function, even when a "more specific" match is available. This probably makes sense but I don't understand C++ dispatch well enough to know why.
When you create methods like this:
virtual bool is_equal(Shape& circle) { return false; };
And in the subclass,
virtual bool is_equal(Circle& circle) { return true; };
These are not the same method. You have two separate virtual methods, neither of which is overridden (they are overloaded not even overloaded, as Ben Voigt pointed out). When you call Shape::is_equal
, there is only one version: Shape::is_equal(Shape&)
... which is not overridden and always returns false.
You would have to define the separate overloaded methods in the parent class and then override them in the child class. For example,
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; };
};
However, using tricks like this, you will probably not approach the performance or simplicity of the way a C programmer would do it:
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:
...;
}
}
If overloading and virtual methods are making your code more complicated than the C version, then you may wish to rethink whether you solve this particular problem with overloading and virtual methods.
相关文章