2

私は現在、脳のおならに苦しんでいます。以前にこれを行ったことがありますが、正確な構文を思い出せず、当時別の会社で働いていたため、書いたコードを見ることができません。私はこの取り決めを持っています:

class P
{
// stuff
};

class PW : public P
{
// more stuff
};

class PR : public P
{
// more stuff
};

class C
{
public:
    P GetP() const { return p; }    
private:
    P p;
};

// ...
    P p = c.GetP( ); // valid
    PW p = c.GetP( ); // invalid
    PR p = c.GetP( ); // invalid
// ...

ここで、P を PW および PR と交換可能にしたいと考えています (したがって、PW と PR は交換可能です)。おそらくキャストを回避できますが、このコードの変更は、このモジュールだけでかなりの回数発生しています。私はそれがオペレーターであると確信していますが、私の人生では何を思い出せません。

最小限のコードで PW および PR と交換可能にするにはどうすればよいですか?

更新:もう少し明確にするために。P は Project を表し、R と W はそれぞれ Reader と Writer を表します。Reader が持っているのは読み込みのためのコードだけです - 変数はなく、Writer は単に Write のためのコードを持っています。読み取りセクションと書き込みセクションには、プロジェクト ファイルの操作であるプロジェクトの本当の関心事ではないさまざまなマネージャー クラスとダイアログがあるため、分離する必要があります。

更新: P と PW のメソッドを呼び出せるようにする必要もあります。したがって、P にメソッド a() があり、PW がメソッド呼び出し b() である場合、次のことができます。

PW p = c.GetP();
p.a();
p.b();

基本的には、変換を透過的にすることです。

4

10 に答える 10

3

ポインターではなく、実際の変数を強制しようとしています。そのためにはキャストが必要です。ただし、クラス定義が次のようになっているとします。

class C

    {
        public: 
            P* GetP() const { return p; }
        private:
            P* p;
    }

次に、p が P、PW、または PR へのポインターであるかどうかにかかわらず、関数は変更されず、関数によって返された P* で呼び出される (仮想) 関数は、P、PW、または PR からの実装を使用します。メンバーpが何であるかに応じて..

覚えておくべき重要なことは、 Liskov Substitution Principleだと思います。PW と PR は P のサブクラスであるため、P であるかのように扱うことができます。ただし、PW を PR として扱うことはできず、その逆も同様です。

于 2008-09-23T16:06:25.747 に答える
3

この部分をコンパイルする場合:



// ...
    P p = c.GetP( ); // valid
    PW p = c.GetP( ); // invalid
    PR p = c.GetP( ); // invalid
// ...

P を PW または PR に構築/変換できる必要があります。次のようなことをする必要があります:



class PW : public P
{
    PW(const P &);
// more stuff
};

class PR : public P
{
    PR(const P &);
// more stuff
};

または、次のような意味でしたか:


class P
{
    operator PW() const;
    operator PR() const;
// stuff
};
于 2008-09-23T17:26:19.783 に答える
2

上記のコードでは、スライスの問題とは逆になります。

あなたがやろうとしているのは、ソースオブジェクトよりも多くの情報を含む P から PW または PR に割り当てることです。これどうやってやるの?P には 3 つのメンバー変数しかないが、PW には 12 の追加メンバーがあるとしますPW p = c.GetP()

この割り当てが実際有効である場合、これは何らかの設計上の奇妙な点を実際に示しているはずであり、 PW::operator=(const P&)and PR::operator=(const P&)PW::PW(const P&)andを実装しPR::PR(const P&)ます。しかし、その夜はあまり眠れませんでした。

于 2008-09-23T15:58:36.403 に答える
1

すべてが値によって渡されることを考えると、この種のことは賢明なことです。それがあなたが考えていたものかどうかはわかりません。

class P
{
public:
    template <typename T>
    operator T() const
    {
        T t;
        static_cast<T&>(t) = *this;
        return t;
    }
};
于 2008-09-23T16:52:37.773 に答える
1

P を介して PW と PR を使用できるようにするには、参照 (またはポインター) を使用する必要があります。したがって、参照を返すように C のインターフェイスを変更する必要はありません。

古いコードの主な問題は、P を PW または PR にコピーしていたことです。PW と PR は P よりも多くの情報を持っている可能性があり、型の観点からは型 P のオブジェクトは PW または PR ではないため、これは機能しません。PWとPRは両方ともPですが。

コードを次のように変更すると、コンパイルされます。実行時に P クラスから派生したさまざまなオブジェクトを返したい場合、クラス C は、期待されるすべてのさまざまな型を潜在的に格納でき、実行時に特殊化できる必要があります。そのため、以下のクラスでは、参照によって返されるオブジェクトへのポインターを渡すことで特殊化できるようにしています。オブジェクトが例外セーフであることを確認するために、ポインターをスマート ポインターでラップしました。

class C
{
    public:
        C(std::auto_ptr<P> x):
            p(x)
        {
            if (p.get() == NULL) {throw BadInit;}
        }
        // Return a reference.
        P& GetP() const { return *p; }        
    private:
        // I use auto_ptr just as an example
        // there are many different valid ways to do this.
        // Once the object is correctly initialized p is always valid.
        std::auto_ptr<P> p;
};

// ...
P&  p = c.GetP( );                   // valid
PW& p = dynamic_cast<PW>(c.GetP( )); // valid  Throws exception if not PW
PR& p = dynamic_cast<PR>(c.GetP( )); // valid  Thorws exception if not PR
// ...
于 2008-09-23T16:20:59.673 に答える
0

2 番目と 3 番目は暗黙のアップキャストであるため無効です。これは C++ では危険なことです。これは、キャスト先のクラスが割り当て先のクラスよりも多くの機能を持っているためです。したがって、明示的に自分でキャストしない限り、C++ コンパイラはエラーをスローします (少なくともそうすべきです)。もちろん、これは物事を少し単純化しています (悪い目的の怒りを呼び起こすことなく、安全にやりたいことに関連する可能性のある特定の事柄に RTTI を使用できます)。

もちろん、他のいくつかの解決策で述べられているように、この問題を回避することはできますが、問題を回避する前に、設計を再考することをお勧めします。

于 2008-09-23T16:09:26.347 に答える
0

おそらく、dynamic_castオペレーターのことでしょうか?

于 2008-09-23T16:01:14.010 に答える
0

それらは完全に交換可能ではありません。PW は P です。PR は P です。ただし、P は必ずしも PW であるとは限りません。また、必ずしも PR であるとは限りません。static_cast を使用して、ポインタを PW * から P * に、または PR * から P * にキャストできます。「スライス」のため、 static_cast を使用して実際のオブジェクトをスーパークラスにキャストしないでください。例)PW のオブジェクトを P にキャストすると、PW の余分なものが「スライス」されます。また、 static_cast を使用して P * から PW * にキャストすることもできません。本当にそうしなければならない場合は、オブジェクトが実際に正しいサブクラスのものであるかどうかを実行時にチェックし、そうでない場合は実行時エラーを与える dynamic_cast を使用してください。

于 2008-09-23T16:03:10.370 に答える
0

正確な意味はわかりませんが、ご容赦ください。

彼らはすでにそうです。すべてを P と呼ぶだけで、PR と PW が P のふりをすることができます。それでもPRとPWは違う。

3 つすべてを同等にすると、リスコフの原理に問題が生じます。しかし、それらが本当に同等であるのに、なぜそれらに異なる名前を付けるのでしょうか?

于 2008-09-23T16:03:34.503 に答える
-1

オブジェクトではなく P への参照またはポインターを使用します。

class C
{
 public:
  P* GetP() const { return p; }
 private:
  P* p;
};

これにより、PW* または PR* を Cp にバインドできます。ただし、P から PW または PR に移動する必要がある場合は、PW* を返す dynamic_cast<PW*>(p) を使用する必要があります。 p のバージョン、または p の NULL は PW* ではありません (たとえば、PR* であるため)。ただし、Dynamic_cast にはいくらかのオーバーヘッドがあり、可能であれば避けるのが最善です (仮想を使用します)。

typeid() 演算子を使用してオブジェクトの実行時の型を判別することもできますが、含める必要があることや余分な派生を検出できないことなど、いくつかの問題があります。

于 2008-09-23T16:00:17.383 に答える