6

私には基本クラスがありPrimitive、そこから他のいくつかのクラスを派生させます-、、SphereなどPlane

Primitiveたとえばintersect()、純粋仮想関数を介してそのサブクラスにいくつかの機能を適用します。の計算はintersectインスタンスデータに依存するため、メンバーメソッドとして使用するのが理にかなっています。

私の問題は次の場合に発生します。すべての派生インスタンスが、たとえばstd::string type()メンバーメソッドを介してそのタイプを識別できるようにしたい。type()同じクラスのすべてのインスタンスは同じ型を返すため、メソッドを作成することは理にかなっていstaticます。また、すべてのPrimitiveサブクラスにこのメソッドを実装してもらいたいので、intersect()上記のように純粋仮想関数にします。

ただし、静的仮想メソッドはC++では許可されていません。 C ++静的仮想メンバー?仮想静的メソッドを持つことができますか?(c ++) 同様の質問をしますが、派生クラスに関数を適用する要件は含まれていません。

誰かが上記で私を助けることができますか?

4

4 に答える 4

6

これについて少し考えてみましょう。サブクラスが 2 つしかないと確信しているので、これを一般化しましょう。

最初に頭に浮かぶのは、コードの重複、拡張性、近さです。これらを拡張しましょう:

さらにクラスを追加したい場合は、可能な限り最小限の場所でコードを変更する必要があります。

intersect操作は可換であるため、交差するコードと交差するコードは、交差するコードAB同じ場所にある必要があるため、クラス自体の内部にロジックを保持することは問題外です。BA

また、新しいクラスを追加しても、既存のクラスを変更する必要はありませんが、デリゲート クラスを拡張する必要があります (はい、ここでパターンを説明します)。

これはあなたの現在の構造だと思います(または同様の、おそらく の戻り値の型ですintersectが、今のところ重要ではありません):

struct Primitive
{
    virtual void intersect(Primitive* other) = 0;
};
struct Sphere : Primitive
{
    virtual void intersect(Primitive* other)
};
struct Plane : Primitive
{
    virtual void intersect(Primitive* other);
};

Planeor内に交差ロジックを入れたくないことはすでに決まっているSphereので、新しい を作成しますclass

struct Intersect
{
    static void intersect(const Sphere&, const Plane&);
    //this again with the parameters inversed, which just takes this
    static void intersect(const Sphere&, const Sphere&);
    static void intersect(const Plane&, const Plane&);
};

これは、新しい関数と新しいロジックを追加するクラスです。たとえば、Lineクラスを追加する場合は、メソッドを追加するだけintersec(const Line&,...)です。

新しいクラスを追加するとき、既存のコードを変更したくないことを思い出してください。そのため、交差関数内の型を確認できません。

この (戦略パターン) の動作クラスを作成できます。これは、タイプに応じて異なる動作をし、後で拡張できます。

struct IntersectBehavior
{  
    Primitive* object;
    virtual void doIntersect(Primitive* other) = 0;
};
struct SphereIntersectBehavior : IntersectBehavior
{
    virtual void doIntersect(Primitive* other)
    {
        //we already know object is a Sphere
        Sphere& obj1 = (Sphere&)*object;
        if ( dynamic_cast<Sphere*>(other) )
            return Intersect::intersect(obj1, (Sphere&) *other);
        if ( dynamic_cast<Plane*>(other) )
            return Intersect::intersect(obj1, (Plane&) *other);

        //finally, if no conditions were met, call intersect on other
        return other->intersect(object);
    }
};

そして、元のメソッドでは、次のようになります。

struct Sphere : Primitive
{
    virtual void intersect(Primitive* other)
    {
        SphereIntersectBehavior intersectBehavior;
        return intersectBehavior.doIntersect(other);
    }
};

さらにクリーンな設計は、ファクトリを実装して、動作の実際のタイプを抽象化することです。

struct Sphere : Primitive
{
    virtual void intersect(Primitive* other)
    {
        IntersectBehavior*  intersectBehavior = BehaviorFactory::getBehavior(this);
        return intersectBehavior.doIntersect(other);
    }
};

intersectすべてのクラスに対してこれを行うだけなので、仮想である必要さえありません。

このデザインに従うと

  • 新しいクラスを追加するときに既存のコードを変更する必要はありません
  • 実装を 1 か所に配置する
  • IntersectBehavior新しいタイプごとにのみ拡張
  • Intersect新しい型のクラスに実装を提供する

そして、これはさらに完成される可能性があるに違いありません。

于 2012-05-08T16:53:36.970 に答える
2

あなたが提供したリンクで彼らが議論した理由により、仮想メンバーを静的にすることはできません。

派生クラスで関数を強制する要件についての質問は、派生クラスが関数を実装する必要があることを強制する抽象基本クラスで関数を純粋仮想にすることによって処理されます。

于 2012-05-08T16:05:00.800 に答える
1

同じクラスのすべてのインスタンスが同じ型を返すため、type() を静的メソッドにすることは理にかなっています。

いいえ、違います。関数を呼び出すためにオブジェクトのインスタンスが必要ない場合は、静的メソッドを使用します。この場合、オブジェクトのタイプを識別しようとしているので、インスタンスが必要です

いずれにせよ、すべてのメソッド本体はすべてのオブジェクトで共有されるため、重複について心配する必要はありません。1 つの例外は、関数がインラインである場合ですが、コンパイラはオーバーヘッドを最小限に抑えるために最善を尽くし、コストが大きすぎる場合は非インラインにすることがあります。

PSクラス階層の外でクラスを識別することを要求することは、通常、コードの悪臭です。別の方法を探してみてください。

于 2012-05-08T17:15:07.327 に答える
0

各派生クラスに適切に実装された静的メソッドを呼び出す (または静的文字列を返す) 非静的仮想メソッドを持つことができます。

#include <iostream>
#include <string>

struct IShape {
  virtual const std::string& type() const =0;
};

struct Square : virtual public IShape {
  virtual const std::string& type() const { return type_; }
  static std::string type_;
};
std::string Square::type_("square");

int main() {

  IShape* shape = new Square;
  std::cout << shape->type() << "\n";

}

type()とにかく、すべてのサブクラスにメソッドを実装する必要があることに注意してください。そのため、できる最善の方法は、文字列を静的にすることです。ただし、文字列のenum代わりに を使用することを検討することもできます。コード内で不要な文字列比較を避けるようにしてください。

さて、問題の根本に戻ると、設計に多少の欠陥があると思います。すべての種類の形状で機能する一般的な交差関数を実際に持つことはできません。これは、同じタイプの形状であっても、交差から生じる形状のタイプが大きく異なるためです (2 つの平面が平面、直線で交差することも、交差しないこともあります)。たとえば、すべて)。したがって、一般的な解決策を提供しようとすると、この種の型チェックをあちこちで実行していることに気付くでしょう。これは、追加する形状が増えるほど、維持できないほど大きくなります。

于 2012-05-08T16:02:33.667 に答える