web-dev-qa-db-ja.com

プライベートな純粋仮想関数のポイントは何ですか?

ヘッダーファイルで次のコードを見つけました。

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

131
BeeBand

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

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

しかし、私の意見では、あなたが提示したコードにはもう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に変換します。

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

_class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};
_
201
Maciej Hehl

プライベートな純粋仮想関数は、非仮想インターフェイスイディオムの基礎です(OK、絶対ではありません常にpurevirtualですが、そこはまだ仮想です)。もちろん、これは他のものにも使用されますが、私はこれが最も便利だと思います(: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()
{
    // ..
}

純粋な仮想-派生クラスにそれを実装する義務を負います。

[〜#〜] edit [〜#〜]:これについての詳細: Wikipedia :: NVI-idiom

42
Kiril Kirov

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

17

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

これらのプライベート関数をオーバーライドできます。たとえば、次の不自然な例は動作します(EDIT:派生クラスメソッドをプライベートにし、派生パターンメソッドの呼び出しをmain()にドロップして、デザインパターンの意図をより明確に示しますuse。):

#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);
}

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

4
Void

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

DevX.com の簡単な説明を見つけることができます。


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

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

3
Buhake Sindi

TL; DR回答:

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

テンプレートメソッド デザインパターンを実装する場合に役立ちます。 protectedを使用できますが、privatevirtualは、カプセル化が優れているため、より適切な選択肢と見なされる場合があります。

0
jaskmar