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 (球が完全に平面の上または下にある場合)

Planeaと aの交差をどのように定義して使用するのが最善なのか疑問に思っていましSphereた。

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

method/free 関数はコンパイル時に不明です。

これまでにこのような状況に遭遇したことはなかったので、いくつかの可能な方法を調べました。を推奨するこの質問に出くわしましたboost::variant。私の状況では、それは次のようになります

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

しかし、これには 3 つの欠点があります。

  1. 醜いです。
  2. andには. intersection.radius_ あなたは次のようなことをしなければならないでしょうPointVoidradius

    if (intersection.isPoint()){
        ...
    }
    else if (intersection.isCircle())
    {
        // possibly cast to Point if degenerate, otherwise:
        double R = intersection.radius;
        ...
    }
    // etc.
    
  3. これらすべての形状を実装するライブラリのユーザーは、2 つの形状が交差することによって返される型を常に把握しておく必要があります。つまり、ユーザーは常に、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()または、 and/orを使用し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 を続行し、(death to ではなく) マクロを使用して、いくつかクリーンアップします。

確かに、私はまだほとんど 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 に答える