148

ヘッダーファイルで次のコードに出くわしました。

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

私にとって、これは、Engineクラスまたはそれから派生したクラスのいずれかが、これらの純粋仮想関数の実装を提供する必要があることを意味します。しかし、派生クラスがそれらを再実装するためにそれらのプライベート関数にアクセスできるとは思いませんでした-それでは、なぜそれらを仮想化するのですか?

4

6 に答える 6

214

トピックの質問は、かなり一般的な混乱を示唆しています。混乱は十分に一般的であり、混乱は悪いことのように思われたため、C++FAQは長い間プライベート仮想の使用に反対していました。

したがって、最初に混乱を取り除くために:はい、プライベート仮想関数は派生クラスでオーバーライドできます。派生クラスのメソッドは、基本クラスから仮想関数を呼び出すことはできませんが、独自の実装を提供することはできます。Herb Sutterによると、基本クラスにパブリック非仮想インターフェイスがあり、派生クラスでカスタマイズできるプライベート実装があると、「実装のカスタマイズ可能な動作の仕様からインターフェイスの仕様を分離」することができます。あなたは彼の記事「仮想性」でそれについてもっと読むことができます。

しかし、私の意見では、あなたが提示したコードにはもう1つ興味深いことがあります。それは、もう少し注意を払う価値があります。パブリックインターフェイスは、オーバーロードされた非仮想関数のセットで構成され、これらの関数は、非パブリック、非オーバーロードの仮想関数を呼び出します。C ++の世界ではいつものように、それはイディオムであり、名前があり、もちろん便利です。名前は(サプライズ、サプライズ!)

「パブリックオーバーロードされた非仮想は、保護された非オーバーロード仮想を呼び出します」

非表示ルールを適切に管理するのに役立ちます。詳細についてはこちらをご覧くださいが、すぐに説明します。

Engineクラスの仮想関数がそのインターフェースでもあり、純粋仮想ではないオーバーロードされた関数のセットであると想像してください。それらが純粋な仮想である場合でも、以下で説明するように同じ問題が発生する可能性がありますが、クラス階層の下位にあります。

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

ここで、派生クラスを作成し、引数として2つのintを受け取るメソッドに対してのみ新しい実装を提供する必要があると仮定します。

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

派生クラスにusing宣言を入れるのを忘れた場合(または2番目のオーバーロードを再定義する場合)、以下のシナリオで問題が発生する可能性があります。

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

メンバーの非表示を防止しなかった場合はEngine、次のステートメントを使用します。

myV8->SetState(5, true);

void SetState( int var, int val )派生クラスから呼び出し、に変換trueintます。

例のように、インターフェースが仮想ではなく、仮想実装が非公開である場合、派生クラスの作成者は、考える問題が1つ少なくなり、簡単に記述できます。

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};
于 2010-10-20T13:48:56.097 に答える
43

プライベートの純粋仮想関数は、非仮想インターフェースのイディオムの基本です(OK、それは常に純粋仮想であるとは限りませんが、それでも仮想です)。もちろん、これは他の目的にも使用されますが、これが最も役立つと思います(:2つの言葉で:パブリック関数では、最初にいくつかの一般的なもの(ロギング、統計など)を置くことができます。関数の最後で、次に「途中で」このプライベート仮想関数を呼び出すと、特定の派生クラスでは異なります。次のようになります。

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

純粋な仮想-派生クラスに実装を義務付けるだけです。

編集:これについての詳細:ウィキペディア::NVI-イディオム

于 2010-10-19T16:12:35.123 に答える
17

たとえば、これにより、派生クラスは、基本クラス(純粋仮想関数宣言を含む)が呼び出すことができる関数を実装できるようになります。

于 2010-10-19T16:04:34.020 に答える
4

編集:オーバーライドする機能とアクセス/呼び出す機能に関するステートメントを明確にしました。

それらのプライベート関数をオーバーライドすることができます。たとえば、次の工夫された例が機能します(編集:派生クラスメソッドをプライベートにし、派生クラスメソッドの呼び出しをドロップしてmain()、使用中のデザインパターンの意図をよりよく示します。):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtualコード内のメソッドのような基本クラスのメソッドは、通常、テンプレートメソッドデザインパターンを実装するために使用されます。このデザインパターンにより、基本クラスのコードを変更せずに、基本クラスのアルゴリズムの動作を変更できます。基本クラスのメソッドが基本クラスのポインターを介して呼び出される上記のコードは、テンプレートメソッドパターンの簡単な例です。

于 2010-10-19T16:40:43.983 に答える
2

プライベート仮想メソッドは、特定の関数をオーバーライドできる派生クラスの数を制限するために使用されます。プライベート仮想メソッドをオーバーライドする必要がある派生クラスは、基本クラスのフレンドである必要があります。

DevX.comの簡単な説明があります。


編集プライベート仮想メソッドは、テンプレートメソッドパターンで効果的に使用されます。派生クラスはプライベート仮想メソッドをオーバーライドできますが、派生クラスはその基本クラスのプライベート仮想メソッドを呼び出すことはできません(この例では、SetStateBoolおよびSetStateInt)。基本クラスのみがそのプライベート仮想メソッドを効果的に呼び出すことができます(派生クラスが仮想関数の基本実装を呼び出す必要がある場合にのみ、仮想関数を保護します)。

Virtualityに関する興味深い記事を見つけることができます。

于 2010-10-19T16:07:05.567 に答える
1

TL; DR回答:

これを別のレベルのカプセル化のように扱うことができます-保護プライベートの間のどこか:子クラスから呼び出すことはできませんが、オーバーライドすることはできます。

テンプレートメソッドデザインパターンを実装するときに役立ちます。保護されたものを使用することもできますが、カプセル化が優れているため、仮想と一緒にプライベートを選択することをお勧めします。

于 2019-08-13T13:13:24.590 に答える