为不同形状实现抽象'重叠'方法?

时间:2013-07-16 11:02:04

标签: c++ class oop override

我有一个名为Shape的抽象基类,它看起来像这样:

class Shape {
    public:
        Shape(Point center);
        virtual bool overlaps(Shape *other) = 0;

    private:
        Point m_center; // has getter&setter
};

我遇到了overlaps(Shape *other);方法的问题;我不知道如何在子类中实现它。

让我们举两个例子,(我可能只有两三个形状)CircleRect。 基本上我尝试过的是在使用前向声明后允许CircleRect到"知道"之后在两个类中创建两个重载。彼此:

virtual bool Rect::overlaps(Circle *other);
virtual bool Rect::overlaps(Rect *other);
virtual bool Circle::overlaps(Circle *other);
virtual bool Circle::overlaps(Rect *other) { return other->overlaps(this); }

现在很容易在所有重载中实现数学;但是,我会收到错误cannot allocate an object of abstract type 'Circle'note: virtual bool Unit::overlaps(Unit *)。 这是因为我的CircleRect类只有Circle *Rect *作为参数的方法,但没有Unit *

我还尝试在我的 shape.h 中声明CircleRect,但由于前向声明与我的实际{{1}不同}和Circle,我只会得到同样的错误。

如果不删除公共基类,有没有办法实现这种行为? 或者是否有解决方法使其工作?

其他信息

我有一个包含Rect的2D World类,我需要看看两个形状是否相互重叠;

vector<Shape *> m_shapes;

3 个答案:

答案 0 :(得分:6)

欢迎多次发货!基本上,您要求的是一个相对于多个对象的运行时类型而言是虚拟的方法 - 在您的情况下,两个形状的类型正在测试重叠。

在C ++中实现双重调度有几种常见方法:例如,您可以使用visitor pattern,或根据RTTI制作地图。选择一个或另一个取决于你。

如果您决定使用访问者模式,则可以通过添加访问方法使Shape“可访问”。

以下是基于访问者的方法示例。它无疑是相当冗长的,但它也解决了一个复杂的任务,所以它需要大量的代码是公平的。我将下面的示例剥离到最低限度 - 只有两个没有数据成员的形状,以及除了打印之外什么都不做的方法。这应该足以让你开始,但是:

#include <iostream>
using namespace std;

class ShapeVisitor;

struct Shape {
    virtual void accept(ShapeVisitor& v) = 0;
    virtual bool overlaps(Shape& other) = 0;
};

class Circle;
class Square;

struct ShapeVisitor {
    virtual void visitCircle(Circle& c) = 0;
    virtual void visitSquare(Square& s) = 0;
};

// These three methods do the actual work
bool checkOverlap(Square& s, Circle& c) {
    cout << "Checking if square overlaps circle" << endl;
    return false;
}
bool checkOverlap(Square& a, Square& b) {
    cout << "Checking if square overlaps square" << endl;
    return false;
}
bool checkOverlap(Circle& a, Circle& b) {
    cout << "Checking if circle overlaps circle" << endl;
    return false;
}

class Square : public Shape {
    struct OverlapVisitor : public ShapeVisitor {
        OverlapVisitor(Square& _my) : result(false), my(_my) {}
        virtual void visitCircle(Circle& c) {
            result = checkOverlap(my, c);
        }
        virtual void visitSquare(Square& s) {
            result = checkOverlap(my, s);
        }
        bool result;
        Square& my;
    };
public:
    virtual void accept(ShapeVisitor& v) {
        v.visitSquare(*this);
    }
    virtual bool overlaps(Shape& other) {
        OverlapVisitor v(*this);
        other.accept(v);
        return v.result;
    }
};

class Circle : public Shape {
    struct OverlapVisitor : public ShapeVisitor {
        OverlapVisitor(Circle& _my) : result(false), my(_my) {}
        virtual void visitCircle(Circle& c) {
            result = checkOverlap(my, c);
        }
        virtual void visitSquare(Square& s) {
            // Important: note how I switched the order of arguments
            // compared to Square::OverlapVisitor! There is only one
            // square/circle overlap function checker, and it expects
            // the square to be the first argument.
            result = checkOverlap(s, my);
        }
        bool result;
        Circle& my;
    };
public:
    virtual void accept(ShapeVisitor& v) {
        v.visitCircle(*this);
    }
    virtual bool overlaps(Shape& other) {
        OverlapVisitor v(*this);
        other.accept(v);
        return v.result;
    }
};

这是正在运行的demo on ideone

使用RTTI方法,你可以创建一个map<pair<type_info,type_info>,checker>,其中checker是一个函数的类型,它指向Shape的两个指针,并返回truefalse,具体取决于是否或不是形状重叠。您为每对对象类型创建一个这样的函数,使用其预期参数类型的type_info使用指向这些函数的指针填充映射,并在运行时使用此映射来调用所需的函数。

More Effective C++书的第31项深入解释了这两种方法,并附有一些很好的例子。实际上,本书中讨论的用于检测一对游戏对象之间冲突的用例与您正在实现的用例类似。

答案 1 :(得分:1)

您需要的是“other”类型函数有多大。如果我们让它变得非常简单,只需使用一个边界框(一个足以覆盖整个形状的矩形),那么我们可以这样做:

(简单来说,我使用rect作为矩形的术语)

class Shape
{
 ...
 virtual rect BoundingBox() = 0;

 bool overlaps(const Shape& other)
 {
     return BoundingBox.FitsInside(other.BoundingBox()); 
 }

};

显然,你必须为两个矩形编写fitsinside的函数,并为每个形状编写BoundingBox,但它不应该太难。

制作一个“Star完全覆盖这个Oval吗?”提出了一个更具挑战性的解决方案[你需要有一个完整的两个形状轮廓,Oval轮廓可能有很多点正好是椭圆形的]。

答案 2 :(得分:1)

让子类彼此了解是一个坏主意。如果你想要像素完美的碰撞,那么你将不得不遍历形状中的每个像素并与其他形状的像素进行比较。创建一个虚函数,从形状中获取像素N,其中N是索引,另一个函数返回像素数。对于当前形状中的每个像素N,与另一个形状中的所有像素0..Nmax进行比较以进行碰撞。

索引N的像素顺序可以是任何顺序。如果您在形状的不同边之间交替使用邻近N的像素,并首先从外部像素开始,您可能更有可能在较低的N上检测到碰撞。

现在这种简单的方法很慢,特别是如果你有很多形状。解决方案是使用更便宜的算法来检查是否需要完美的算法。矩形边界框是最便宜的方式。计算出一个足够大的矩形坐标,以保持你的形状。我不知道如何解决这个问题(几何不是我强壮的西装)。您甚至可以缓存类中的边界框大小,以防止重新计算复杂的形状。检查两个矩形是否重叠是非常快速和容易的。

如果边界框重叠,则只需转移到昂贵的算法上。

您可以在某些对象之间进行更快速的检查。例如,如果它们的边界框有两个矩形重叠。进行像素比较是过度的。但是你可能不需要这种性能水平。