8

想象一下,我有抽象基类 Shape、派生类CircleRectangle.

class Shape {};
class Circle : public Shape {};
class Rectangle : public Shape {};

Shape*假设我有两个指针,我需要确定两个形状是否相等。(这是因为我有两个实例,vector<Shape*>我想看看它们是否具有相同的形状。)

推荐的方法是双重调度。我想出的是这个(这里大大简化了,所以形状等于所有其他相同类型的形状):

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函数复制粘贴到每个派生类中。这是一个非常多的样板,比如 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++ 调度的理解不够深入,无法知道原因。

4

6 回答 6

5

当您创建这样的方法时:

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 版本更复杂,那么您可能希望重新考虑是否使用重载和虚方法解决这个特定问题。

于 2011-09-12T20:24:46.570 回答
5

双重调度已经得到很好的研究。双调度的泛化称为“多方法”。

现代 C++ 设计的第 11 章详细讨论了这个问题。使用dynamic_cast<>您描述的方法在第 11.3 节“双开关类型:蛮力”中。作者甚至描述了如何自动化大部分工作并自动生成对称重载。然后,作者介绍了一种基于std::map<>和的对数调度std::type_info。最后,该部分以“恒定时间多方法:原始速度”结束,它(大致)基于回调函数矩阵。

提出的解决方案包括对处理函子和强制转换的冗长解释,以避免在存在多重(和虚拟)继承时出现令人讨厌的陷阱。

如果您考虑在 C++ 中实现多方法,我强烈建议您阅读本书并实现建议的解决方案。

于 2011-09-12T21:53:23.670 回答
1

dynamic_cast如果太慢,您可以使用类型枚举和静态转换...

enum ShapeType
{
    SHAPE_TYPE_CIRCLE,
    SHAPE_TYPE_RECTANGLE
};

struct Shape
{
    virtual ShapeType GetShapeType() const = 0;
    virtual bool isEqual(const Shape& other) const = 0;
};

struct Circle : Shape
{
    virtual ShapeType GetShapeType() const { return SHAPE_TYPE_CIRCLE; }

    virtual bool isEqual(const Shape& other) const
    {
        if (other.GetShapeType() == SHAPE_TYPE_CIRCLE)
        {
            const Circle *circle = static_cast<const Circle*>(&other);

            // do some circle specific comparison
            return true;
        }
        return false;
    }
};
于 2011-09-12T20:32:23.677 回答
0

我通常指的是dynamic_cast和virtual funcntions。如果编译器不是太笨,那么动态转换一步与在 vtable 中进行两次跳转没有什么不同。

class shape
{
protected:
   virtual bool is_equal(const shape* s) const=0;
   friend bool oeprator==(const shape& a, cost shape& b)
   { return a.is_equal(&b); }
};

class circle: public shape
{
    double radius;
    point<duouble> center;
protected:
    virtual bool is_equal(const shape* s) const
    {
        const circle* p = dynamic_cast<const circle*>(s);
        return p && p->radius==radius && p->center==center;
    }
};

矩形或任何其他形状也是如此。基本上,双重调度需要 - 如果 N 是分类对象,则有 N 2 个函数。这样,您只需要 N 个函数(每个类一个)。

如果您觉得动态转换太慢,您可以使用枚举,在基类中声明,并由派生类正确初始化。但这需要您在每次添加新类时更新枚举值。例如:class shape { protected: enum shapes_type { no_shape, circle_shape, rectangle_shape }; 形状类型我的类型;virtual bool is_equal(const shape* s) const=0; 朋友 bool oeprator==(const shape& a, cost shape& b) { return a.is_equal(&b); } 形状() :my_type(no_shape) {} };

class circle: public shape
{
    double radius;
    point<duouble> center;
protected:
    virtual bool is_equal(const shape* s) const
    {
        const circle* p = static_cast<const circle*>(s);
        return my_type == s->my_type && p->radius==radius && p->center==center;
    }
public:
    circle() { my_type = circle_shape; }
};

如果不可接受依赖 base_defined 枚举(未知数量的可能类),您可以依赖一个简单的值(例如:整数),它可以用以下技巧明确表示类型:

int int_generator()
{ static int x=0; return ++x; }

template<class T>
int  id_for_type()
{ static int z = int_generator(); return z; }

class shape
{
...
int my_type;
};


class circle
{
...
   circle() { my_type = id_for_type<circle>(); }
};
于 2011-09-12T21:09:15.100 回答
0

虚函数可以轻松替代dynamic_castRTTI 类型检查,如下所示:http: //ideone.com/l7Jr5

struct Shape
{
    struct subtype { enum { Shape, Circle, Rectangle, ColoredCircle }; };

    virtual bool is_a( int type ) const { return type == subtype::Shape; }
    virtual bool is_equal(const Shape& s) const { return false; }
};

struct Rectangle : Shape
{
    virtual bool is_a( int type ) const { return type == subtype::Rectangle || Shape::is_a(type); }
    virtual bool is_equal(const Shape& s) const
    {
        if (!s.is_a(subtype::Rectangle)) return false;
        const Rectangle& r = static_cast<const Rectangle&>(s);
        return true; // or check width and height
    }
};

struct Circle : Shape
{
    virtual bool is_a( int type ) const { return type == subtype::Circle || Shape::is_a(type); }
    virtual bool is_equal(const Shape& s) const
    {
        if (!s.is_a(subtype::Circle)) return false;
        const Circle& c = static_cast<const Circle&>(s);
        return true; // or check radius
    }
};

struct ColoredCircle : Circle
{
    virtual bool is_a( int type ) const { return type == subtype::ColoredCircle || Circle::is_a(type); }
};

int main(void)
{
    Rectangle x;
    Shape y;
    return x.is_equal(y);
}

--

为什么有 10 个“完全相同”功能的副本?不Rectangle::is_equal(const Rectangle&) const应该比较特定于矩形的成员吗?

如果所有矩形都属于一个等价类,就像您展示的代码一样,那么您可以只拥有一个返回等价类的虚拟函数。

于 2011-09-12T21:16:29.613 回答
0

在我的设计中,我将Shape::operator==方法移动到私有而不是实现它。正确解决这个问题的工作量是不值得的。

换句话说,给定一个向量Shape *

std::vector<Shape *> my_shapes;

我可以执行以下操作:

my_shapes.push_back(new Rectangle);
my_shapes.push_back(new Circle);

比较对象时出现问题:

Shape * p_shape_1 = my_shapes[0];
Shape * p_shape_2 = my_shapes[1];
if (*p_shape_1 == *p_shape_2) {...}

表达式等价于:

p_shape_1->运算符==(*p_shape_2);

如果有虚拟或多态操作,则变为:

矩形::运算符==( (圆));

换句话说,矩形很有可能会将自己与圆形或其他形状进行比较;无效的比较。

因此,在我的设计中,我禁止基于基类指针的相等比较。可以使用指向基类的指针进行比较的唯一内容是基类中的内容。

于 2011-09-13T00:54:39.827 回答