2

假设您有以下内容:

class Shape  // base class
{
private:
    bool degenerate, ill_defined;
    ...
public:
    bool isVoid  () { return false; }
    bool isCircle() { return false; }
    bool isPoint () { return false; }
    bool isPlane () { return false; }
    bool isSphere() { return false; }
    ...
};

class Void : public Shape {
    ...
}

class Plane : public Shape
{
public:
    bool isPlane() { return !degenerate && !ill_defined; }
    bool isVoid () { return ill_defined; }
    ...
    operator Void () throw() { 
        if (isVoid()) return Void(); 
        else throw ...; //some error
    }
    ...
}

class Point : public Shape {
private:
    double radius;
    ...
public:
    bool isPoint() { return !ill_defined; }
    bool isVoid () { return ill_defined; }
    ...        
    operator Void () throw() { ... }
    ...
}

class Circle : public Shape // similar to the rest

class Sphere : public Shape // similar to the rest

Planea和 a之间的交集Sphere可以是

  • a Circle(如果平面“穿过”球体)
  • a Point (如果平面“刚刚接触”球体)
  • a Void (如果球体完全位于平面上方或下方)

我想知道如何最好地定义和使用 aPlane和 a之间的交集Sphere,因为假设的返回类型

intersect(const Sphere& S, const Plane& P)

编译时方法/自由函数是未知的。

我以前从未遇到过这种情况,所以我查找了一些可能的方法来做到这一点。我遇到了这个建议的问题boost::variant。在我的情况下,这看起来像

boost::variant<Void, Point, Circle> intersection = 
    intersect(const Sphere& S, const Plane& P);

但这有三个缺点:

  1. 很丑
  2. 之类的东西intersection.radius不能按原样使用,因为PointandVoid没有radius. 你必须做类似的事情

    if (intersection.isPoint()){
        ...
    }
    else if (intersection.isCircle())
    {
        // possibly cast to Point if degenerate, otherwise:
        double R = intersection.radius;
        ...
    }
    // etc.
    
  3. 实现所有这些形状的库的用户总是必须知道通过交叉两个形状可以返回哪些类型。也就是说,用户总是必须声明一些boost::variant<scope::Void, scope::Point, scope::Circle>复杂且丑陋的类型。幸运的是,c++11 有这个auto关键字。或者,您可以使用这样的成员

    class Sphere : public Shape
    {
        ...
    public: 
    
        boost::variant<scope::Void, scope::Point, scope::Circle>
            intersect_type;
    
        intersect_type intersect(const Plane& P);
    
        ...
    };
    

    这样我们就可以使用

    Sphere::intersect_type t = S.intersect(P);
    

    其中S是 的一个实例SphereP的一个实例Plane。但是我们仍然必须单独处理所有可能的类型:

    if (intersection.isPoint()){
        ...
    }
    else if (intersection.isCircle()){
        intersection.radius;
    }
    // etc.
    

所以我们试图从用户那里带走的复杂性实际上仍然存在。

我觉得我在这里遗漏了一些东西。也许有一种更聪明的方法来实现我的Shape基类?还是我应该创建一个单独的专用Intersect课程?对于这种情况,哪种解决方案最优雅、最有效和最有效?

4

3 回答 3

3

副手:

谓词方法对isXXXX()我来说似乎是一种代码味道。你会做的

  • if (dynamic_cast<Circle*>(shapePtr))通常使用 RTTI
  • 或使用variant::which()和/或variant::type()区分变体的存储值

对于你的问题:

有几种可能的方法。

  1. 经典的 OO 方法是从 Shape 派生所有内容并始终返回 a std::unique_ptr<Shape>(或类似的)。

  2. 但是,显然,您可以执行现代 C++静态OO,在这种情况下,您最终会得到类似于变体的东西。然后你会写一个访问者来处理不同的情况:

(在http://liveworkspace.org/code/bad329cb40d94a21531e1153f4c0877b上直播)

#include <string>
#include <iostream>
#include <boost/lexical_cast.hpp>
#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>

struct Shape 
{ 
    /*virtual*/ double getSurface() const { return 42.0; }  // TODO
};

struct Circle : Shape {};
struct Point : Shape {};
struct Rect : Shape {};

struct Nil {};

typedef boost::variant<Nil, Circle, Point, Rect> Intersect;

struct DescribeVisitor : boost::static_visitor<std::string>
{
    std::string operator()(Circle const& s) const {
        return std::string("Got a circle of ") + boost::lexical_cast<std::string>(s.getSurface());
    }

    std::string operator()(Rect const& s) const {
        return std::string("Got a rectangle of ") + boost::lexical_cast<std::string>(s.getSurface());
    }

    std::string operator()(Point const& s) const {
        return std::string("Got a point of ") + boost::lexical_cast<std::string>(s.getSurface()); // mmm bit funny :)
    }

    std::string operator()(Nil const&) const {
        return std::string("Got an empty intersection");
    }
};

std::ostream& operator<<(std::ostream& os, Intersect const& i)
{
    return os << boost::apply_visitor(DescribeVisitor(), i);
}

int main(int argc, const char *argv[])
{
    Intersect describe = Point();
    std::cout << describe << std::endl;

    describe = Rect();
    std::cout << describe << std::endl;

    describe = Circle();
    std::cout << describe << std::endl;
}

输出:

Got a point of 42
Got a rectangle of 42
Got a circle of 42
于 2012-10-03T09:35:12.957 回答
1

在我看来,你可以:

1) 创建返回 void * 的重载运算符/函数,然后将链接到一个保存其真实返回值的成员变量,以及另一个具有其类型的成员函数。

2)使用模板,虽然这可能比它的价值更麻烦。

3) 继续使用 boost::variant,并使用(但不是死)宏清理它一些。

当然,我仍然主要习惯于 C++98,ergo auto 只是我开始玩的东西。它也可能在回报中提供安慰,因为我相信它是在运行时确定的。

于 2012-10-03T09:35:06.867 回答
1

如果与您的形状类型的交互严格依赖于特定类型并且不能简化为可以以多态方式使用的一些统一的基本接口,那么唯一可以帮助您管理复杂性的是某种形式的Visitor模式。您可以使其对您的类层次结构具有侵入性,但boost::variant已经对其提供了有用的支持 - boost::static_visitor

// Define processing for intersection types
struct IntersectionProcessor: boost::static_visitor<>
{
    void operator()(Sphere&)
    {
       // Process Sphere
    }

    void operator()(Point&)
    {
       // Process Point
    }

    void operator()(Void&)
    {
       // Process Void
    }

    template <typename T> void operator()(T&)
    {
       // Process any other shape
    }
};

// Usage
Sphere A;
Plane B;
auto intersectResult = intersect(A, B);
boost::apply_visitor(IntersectionProcessor(), intersectResult);

// Also easy to use as functor applied to container of results:
std::vector<intersect_type> intResVec = getIntersectionResults();
std::for_each(intResVec.begin(), intResVec.end(),
   boost::apply_visitor(IntersectionProcessor));
于 2012-10-03T09:54:22.090 回答