可变回报类型

时间:2012-10-03 09:25:25

标签: c++ boost variable-assignment

假设您有以下内容:

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

PlaneSphere之间的交集可以是

  • a Circle(如果飞机“穿过”球体)
  • a Point(如果飞机“只是触及”球体)
  • a Void(如果球体完全位于飞机的上方或下方)

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

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

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

我之前从未遇到过这种情况,所以我找到了一些可行的方法。我遇到this question推荐boost::variant。在我的情况下,这看起来像

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

但这有三个缺点:

  1. 这很难过。
  2. intersection.radius之类的内容无法按原样使用,因为PointVoid没有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);
    

    其中SSphere的实例,PPlane的实例。但是,我们仍然必须单独处理所有可能的类型:

    if (intersection.isPoint()){
        ...
    }
    else if (intersection.isCircle()){
        intersection.radius;
    }
    // etc.
    
  4. 因此,我们试图从用户手中夺走的复杂性实际上仍然存在。

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

3 个答案:

答案 0 :(得分:3)

副手:

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

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

问题:

有几种可能的方法。

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

  2. 但是,显然,您可以使用现代C ++ 静态 OO,在这种情况下,您最终会得到类似于变体的内容。然后,您可以编写访问者来处理不同的案例:

  3. (住在 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
    

答案 1 :(得分:1)

在我看来,你可以:

1)创建返回void *的重载操作符/函数,而后者又链接到保存它的真实返回值的成员变量,以及带有它类型的另一个成员函数。

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

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

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

答案 2 :(得分:1)

如果与形状类型的交互严格依赖于特定类型,并且无法简化为可以多态方式使用的统一基本接口,那么唯一可以帮助您管理复杂性的是{{3}模式。您可以使用类层次结构使其变得干扰,但Visitor已经为它提供了有用的支持 - boost::variant

// 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));