web-dev-qa-db-ja.com

未使用の継承された仮想関数のベストプラクティス(スーパークラスメソッドの実装)

仮想関数を使用して互いに派生したいくつかのクラスの階層があるとしましょう。基本クラスはインターフェースとして機能し、すべての派生クラスで必要とされない可能性がある関数を定義します。このような中間関数のベストプラクティスは何でしょうか。それらはまだ実装されており、基本クラスに至るまで単純なチェーンにすべきですか、それともスキップすべきですか?私が抱えている問題は、そのような関数を実装する必要がない場合、コードにノイズを追加するだけです。一方、より高いクラスを使用する場合、C++では(Javaのように)スーパークラスメソッドを単に呼び出すことはできないため、メソッドが実際に実装されている基本クラスを知る必要があります。そして、私の考えでは、これはカプセル化に違反します。これは、基本クラスの実装の詳細を知っている必要があるためです。

ここで私が意味することを説明するために、人工的な例を示します。

_class MyInterface
{
    virtual void foo(bool) = 0;
    virtual void foo1(int) = 0;
};

class Base : public MyInterface
{
    void foo(bool param) override
    {
        // do some stuff here.
    }

    void foo1(int param) override
    {
        // do some stuff here.
    }
};


class A : Base
{
    void foo(bool param) override
    {
        // do some stuff here.
        Base::foo(param);
    }
    // class doesn't need foo1() here so it is not implemented.
};


class B : A
{
    void foo(bool param) override
    {
        // do some stuff here.
        A::foo(param);
    }

    void foo1(int param) override
    {
        // This class needs foo1() but as A doesn't have it implemented
        // I have to knwo this and skip A going directly to Base
        Base::foo1(1);
    }
};
_

ここで、クラスBは、Aが_foo1_を実装しておらず、スキップする必要があることを知る必要があります。もちろん、これはヘッダーで確認できますが、後でクラスAを変更したい場合は、それから派生した他のすべてのクラス(カプセル化と分離の原則)を変更する必要はありません。 、後でAfoo1()を後で実装しても、Bはそれをスキップし、機能が変更されたことに気付くことはありません。

オブジェクトモデルの観点からは、そのような空の関数はクラスに設計する必要があると思いますか?

1
Devolus

そうです、これは言語自体の欠陥です。つまり、継承から得られるすべての利点にもかかわらず、まだそれを知る必要があります実装されています。メソッド内でsuperを呼び出し、適切なメソッドを呼び出せるようにすることが理想的です。これは、Javaが改善しようとしたことの1つです。

あなたが言ったように、クラスAのすべてのメソッドを実装することによってそれを回避することができ、そうすることで、後で複雑化を防ぐことができます。少々苦痛ですが、他の人が使うための適切なライブラリを作成するのに必要な悪だと思います。ライブラリで使用できるようにするクラスだけがすべての仮想メソッドを実装する必要があり、特に必要がない限り、内部で使用されるクラスは実装しないことに注意してください。

解決策ではありませんが、少し親切なアプローチ(少しハッキーですが)で、基本クラスのプライベート型定義としてtypedef "super"を使用することもできます。クラスBの場合と同様に、次のようになります。

private:  
    typedef A super;

同様にクラスAでは、次のように定義できます。

private:
    typedef Base super;

少なくとも、クラスの名前を知っている必要はなく、コードを書いているのであれば、次のようにすることができます。

class B : A
{
    void foo(bool param) override
    {
        // do some stuff here.
        super::foo(param);
    }

    void foo1(int param) override
    {
        super::super::foo1(1);
    }
};

繰り返しになりますが、これで問題が解決するわけではありませんが、少し管理しやすくなります。

詳細はこちらをご覧ください。

1
Neil

関数の直接の基本クラスバージョンを呼び出すだけです。その直接の基本クラスにその関数が実装されていない場合、コンパイラーはその基本の親の関数を呼び出し、実際に関数を実装する祖先が見つかるまで続けます。

1
Hamish Fawns